Change visudo -x to take a file name argument, which may be '-' to

write the exported sudoers file to stdout.
This commit is contained in:
Todd C. Miller
2013-12-16 14:32:42 -07:00
parent b2c456341a
commit 68f6e23b07
5 changed files with 231 additions and 191 deletions

View File

@@ -4,7 +4,7 @@ NNAAMMEE
vviissuuddoo - edit the sudoers file
SSYYNNOOPPSSIISS
vviissuuddoo [--cchhqqssVVxx] [--ff _s_u_d_o_e_r_s]
vviissuuddoo [--cchhqqssVV] [--ff _s_u_d_o_e_r_s] [--xx _f_i_l_e]
DDEESSCCRRIIPPTTIIOONN
vviissuuddoo edits the _s_u_d_o_e_r_s file in a safe fashion, analogous to vipw(1m).
@@ -72,12 +72,14 @@ DDEESSCCRRIIPPTTIIOONN
--VV, ----vveerrssiioonn
Print the vviissuuddoo and _s_u_d_o_e_r_s grammar versions and exit.
--xx, ----eexxppoorrtt
Export _s_u_d_o_e_r_s in JSON format to the standard output. The
result is intended to be easier for third-party applications
to parse that the normal _s_u_d_o_e_r_s format. The various values
have explicit types which removes some of the ambiguity of
the _s_u_d_o_e_r_s format.
--xx _f_i_l_e, ----eexxppoorrtt=_f_i_l_e
Export _s_u_d_o_e_r_s in JSON format and write it to _f_i_l_e. If _f_i_l_e
is `-', the exported _s_u_d_o_e_r_s policy will to be written to the
standard output. The exported format is intended to be
easier for third-party applications to parse that the
traditional _s_u_d_o_e_r_s format. The various values have explicit
types which removes much of the ambiguity of the _s_u_d_o_e_r_s
format.
EENNVVIIRROONNMMEENNTT
The following environment variables may be consulted depending on the
@@ -154,4 +156,4 @@ DDIISSCCLLAAIIMMEERR
file distributed with ssuuddoo or http://www.sudo.ws/sudo/license.html for
complete details.
Sudo 1.8.9 November 15, 2013 Sudo 1.8.9
Sudo 1.8.9 December 16, 2013 Sudo 1.8.9

View File

@@ -21,7 +21,7 @@
.\" Agency (DARPA) and Air Force Research Laboratory, Air Force
.\" Materiel Command, USAF, under agreement number F39502-99-1-0512.
.\"
.TH "VISUDO" "@mansectsu@" "November 15, 2013" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.TH "VISUDO" "@mansectsu@" "December 16, 2013" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -30,8 +30,9 @@
.SH "SYNOPSIS"
.HP 7n
\fBvisudo\fR
[\fB\-chqsVx\fR]
[\fB\-chqsV\fR]
[\fB\-f\fR\ \fIsudoers\fR]
[\fB\-x\fR\ \fIfile\fR]
.SH "DESCRIPTION"
\fBvisudo\fR
edits the
@@ -204,15 +205,23 @@ and
\fIsudoers\fR
grammar versions and exit.
.TP 12n
\fB\-x\fR, \fB\--export\fR
\fB\-x\fR \fIfile\fR, \fB\--export\fR=\fIfile\fR
Export
\fIsudoers\fR
in JSON format to the standard output.
The result is intended to be easier for third-party applications
to parse that the normal
in JSON format and write it to
\fIfile\fR.
If
\fIfile\fR
is
`-',
the exported
\fIsudoers\fR
policy will to be written to the standard output.
The exported format is intended to be easier for third-party
applications to parse that the traditional
\fIsudoers\fR
format.
The various values have explicit types which removes some of the
The various values have explicit types which removes much of the
ambiguity of the
\fIsudoers\fR
format.

View File

@@ -19,7 +19,7 @@
.\" Agency (DARPA) and Air Force Research Laboratory, Air Force
.\" Materiel Command, USAF, under agreement number F39502-99-1-0512.
.\"
.Dd November 15, 2013
.Dd December 16, 2013
.Dt VISUDO @mansectsu@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -27,10 +27,13 @@
.Nd edit the sudoers file
.Sh SYNOPSIS
.Nm visudo
.Op Fl chqsVx
.Op Fl chqsV
.Bk -words
.Op Fl f Ar sudoers
.Ek
.Bk -words
.Op Fl x Ar file
.Ek
.Sh DESCRIPTION
.Nm visudo
edits the
@@ -197,15 +200,23 @@ Print the
and
.Em sudoers
grammar versions and exit.
.It Fl x , -export
.It Fl x Ar file , Fl -export Ns No = Ns Ar file
Export
.Em sudoers
in JSON format to the standard output.
The result is intended to be easier for third-party applications
to parse that the normal
in JSON format and write it to
.Ar file .
If
.Ar file
is
.Ql - ,
the exported
.Em sudoers
policy will to be written to the standard output.
The exported format is intended to be easier for third-party
applications to parse that the traditional
.Em sudoers
format.
The various values have explicit types which removes some of the
The various values have explicit types which removes much of the
ambiguity of the
.Em sudoers
format.

View File

@@ -114,7 +114,7 @@ static void help(void) __attribute__((__noreturn__));
static void usage(int);
static void visudo_cleanup(void);
extern bool export_sudoers(const char *, bool, bool);
extern bool export_sudoers(const char *, const char *, bool, bool);
extern void sudoerserror(const char *);
extern void sudoersrestart(FILE *);
@@ -127,10 +127,10 @@ struct passwd *list_pw;
static struct sudoersfile_list sudoerslist = TAILQ_HEAD_INITIALIZER(sudoerslist);
static struct rbtree *alias_freelist;
static bool checkonly;
static const char short_opts[] = "cf:hqsVx";
static const char short_opts[] = "cf:hqsVx:";
static struct option long_opts[] = {
{ "check", no_argument, NULL, 'c' },
{ "export", no_argument, NULL, 'x' },
{ "export", required_argument, NULL, 'x' },
{ "file", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ "quiet", no_argument, NULL, 'q' },
@@ -147,7 +147,8 @@ main(int argc, char *argv[])
struct sudoersfile *sp;
char *args, *editor, *sudoers_path;
int ch, exitcode = 0;
bool quiet, strict, oldperms, export;
bool quiet, strict, oldperms;
const char *export_path;
debug_decl(main, SUDO_DEBUG_MAIN)
#if defined(SUDO_DEVEL) && defined(__OpenBSD__)
@@ -174,7 +175,8 @@ main(int argc, char *argv[])
/*
* Arg handling.
*/
checkonly = oldperms = quiet = strict = export = false;
checkonly = oldperms = quiet = strict = false;
export_path = NULL;
sudoers_path = _PATH_SUDOERS;
while ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (ch) {
@@ -201,7 +203,7 @@ main(int argc, char *argv[])
quiet = true; /* quiet mode */
break;
case 'x':
export = true; /* export mode */
export_path = optarg; /* export mode */
break;
default:
usage(1);
@@ -227,8 +229,8 @@ main(int argc, char *argv[])
exitcode = check_syntax(sudoers_path, quiet, strict, oldperms) ? 0 : 1;
goto done;
}
if (export) {
exitcode = export_sudoers(sudoers_path, quiet, strict) ? 0 : 1;
if (export_path != NULL) {
exitcode = export_sudoers(sudoers_path, export_path, quiet, strict) ? 0 : 1;
goto done;
}
@@ -1317,7 +1319,7 @@ static void
usage(int fatal)
{
(void) fprintf(fatal ? stderr : stdout,
"usage: %s [-chqsVx] [-f sudoers]\n", getprogname());
"usage: %s [-chqsV] [-f sudoers] [-x file]\n", getprogname());
if (fatal)
exit(1);
}
@@ -1328,12 +1330,12 @@ help(void)
(void) printf(_("%s - safely edit the sudoers file\n\n"), getprogname());
usage(0);
(void) puts(_("\nOptions:\n"
" -c, --check check-only mode\n"
" -f, --file=file specify sudoers file location\n"
" -h, --help display help message and exit\n"
" -q, --quiet less verbose (quiet) syntax error messages\n"
" -s, --strict strict syntax checking\n"
" -V, --version display version information and exit\n"
" -x, --export export sudoers in JSON format"));
" -c, --check check-only mode\n"
" -f, --file=file specify sudoers file location\n"
" -h, --help display help message and exit\n"
" -q, --quiet less verbose (quiet) syntax error messages\n"
" -s, --strict strict syntax checking\n"
" -V, --version display version information and exit\n"
" -x, --export=file export sudoers in JSON format"));
exit(0);
}

View File

@@ -74,6 +74,7 @@ struct json_value {
* Closure used to store state when iterating over all aliases.
*/
struct json_alias_closure {
FILE *fp;
const char *title;
unsigned int count;
int alias_type;
@@ -108,10 +109,10 @@ static const char *digest_names[] = {
* Print "indent" number of blank characters.
*/
static void
print_indent(int indent)
print_indent(FILE *fp, int indent)
{
while (indent--)
putchar(' ');
putc(' ', fp);
}
/*
@@ -119,7 +120,7 @@ print_indent(int indent)
* Does not support unicode escapes.
*/
static void
print_string_json_unquoted(const char *str)
print_string_json_unquoted(FILE *fp, const char *str)
{
char ch;
@@ -128,30 +129,30 @@ print_string_json_unquoted(const char *str)
case '"':
case '\\':
case '/':
putchar('\\');
putc('\\', fp);
break;
case '\b':
ch = 'b';
putchar('\\');
putc('\\', fp);
break;
case '\f':
ch = 'f';
putchar('\\');
putc('\\', fp);
break;
case '\n':
ch = 'n';
putchar('\\');
putc('\\', fp);
break;
case '\r':
ch = 'r';
putchar('\\');
putc('\\', fp);
break;
case '\t':
ch = 't';
putchar('\\');
putc('\\', fp);
break;
}
putchar(ch);
putc(ch, fp);
}
}
@@ -160,49 +161,49 @@ print_string_json_unquoted(const char *str)
* Does not support unicode escapes.
*/
static void
print_string_json(const char *str)
print_string_json(FILE *fp, const char *str)
{
putchar('\"');
print_string_json_unquoted(str);
putchar('\"');
putc('\"', fp);
print_string_json_unquoted(fp, str);
putc('\"', fp);
}
/*
* Print a JSON name: value pair with proper quoting and escaping.
*/
static void
print_pair_json(const char *pre, const char *name,
print_pair_json(FILE *fp, const char *pre, const char *name,
const struct json_value *value, const char *post, int indent)
{
debug_decl(print_pair_json, SUDO_DEBUG_UTIL)
print_indent(indent);
print_indent(fp, indent);
/* prefix */
if (pre != NULL)
fputs(pre, stdout);
fputs(pre, fp);
/* name */
print_string_json(name);
putchar(':');
putchar(' ');
print_string_json(fp, name);
putc(':', fp);
putc(' ', fp);
/* value */
switch (value->type) {
case JSON_STRING:
print_string_json(value->u.string);
print_string_json(fp, value->u.string);
break;
case JSON_ID:
printf("%u", (unsigned int)value->u.id);
fprintf(fp, "%u", (unsigned int)value->u.id);
break;
case JSON_NUMBER:
printf("%d", value->u.number);
fprintf(fp, "%d", value->u.number);
break;
case JSON_NULL:
fputs("null", stdout);
fputs("null", fp);
break;
case JSON_BOOL:
fputs(value->u.boolean ? "true" : "false", stdout);
fputs(value->u.boolean ? "true" : "false", fp);
break;
case JSON_OBJECT:
fatalx("internal error: can't print JSON_OBJECT");
@@ -214,28 +215,29 @@ print_pair_json(const char *pre, const char *name,
/* postfix */
if (post != NULL)
fputs(post, stdout);
fputs(post, fp);
debug_return;
}
/*
* Print a JSON string with optional prefix and postfix to stdout.
* Print a JSON string with optional prefix and postfix to fp.
* Strings are not quoted but are escaped as per the JSON spec.
*/
static void
printstr_json(const char *pre, const char *str, const char *post, int indent)
printstr_json(FILE *fp, const char *pre, const char *str, const char *post,
int indent)
{
debug_decl(printstr_json, SUDO_DEBUG_UTIL)
print_indent(indent);
print_indent(fp, indent);
if (pre != NULL)
fputs(pre, stdout);
fputs(pre, fp);
if (str != NULL) {
print_string_json_unquoted(str);
print_string_json_unquoted(fp, str);
}
if (post != NULL)
fputs(post, stdout);
fputs(post, fp);
debug_return;
}
@@ -245,15 +247,15 @@ printstr_json(const char *pre, const char *str, const char *post, int indent)
* that closes the object.
*/
static void
print_command_json(struct sudo_command *c, int indent, bool last_one)
print_command_json(FILE *fp, struct sudo_command *c, int indent, bool last_one)
{
struct json_value value;
const char *digest_type;
debug_decl(print_command_json, SUDO_DEBUG_UTIL)
printstr_json("{", NULL, NULL, indent);
printstr_json(fp, "{", NULL, NULL, indent);
if (c->digest != NULL) {
putchar('\n');
putc('\n', fp);
indent += 4;
if (c->digest->digest_type < SUDO_DIGEST_INVALID) {
digest_type = digest_names[c->digest->digest_type];
@@ -262,31 +264,31 @@ print_command_json(struct sudo_command *c, int indent, bool last_one)
}
value.type = JSON_STRING;
value.u.string = c->digest->digest_str;
print_pair_json(NULL, digest_type, &value, ",\n", indent);
print_pair_json(fp, NULL, digest_type, &value, ",\n", indent);
} else {
putchar(' ');
putc(' ', fp);
indent = 0;
}
if (c->args != NULL) {
printstr_json("\"", "command", "\": ", indent);
printstr_json("\"", c->cmnd, " ", 0);
printstr_json(NULL, c->args, "\"", 0);
printstr_json(fp, "\"", "command", "\": ", indent);
printstr_json(fp, "\"", c->cmnd, " ", 0);
printstr_json(fp, NULL, c->args, "\"", 0);
} else {
value.type = JSON_STRING;
value.u.string = c->cmnd;
print_pair_json(NULL, "command", &value, NULL, indent);
print_pair_json(fp, NULL, "command", &value, NULL, indent);
}
if (c->digest != NULL) {
indent -= 4;
putchar('\n');
print_indent(indent);
putc('\n', fp);
print_indent(fp, indent);
} else {
putchar(' ');
putc(' ', fp);
}
putchar('}');
putc('}', fp);
if (!last_one)
putchar(',');
putchar('\n');
putc(',', fp);
putc('\n', fp);
debug_return;
}
@@ -337,8 +339,8 @@ defaults_to_word_type(int defaults_type)
* that closes the object.
*/
static void
print_member_json(struct member *m, enum word_type word_type, bool last_one,
int indent)
print_member_json(FILE *fp, struct member *m, enum word_type word_type,
bool last_one, int indent)
{
struct json_value value;
const char *typestr;
@@ -390,7 +392,7 @@ print_member_json(struct member *m, enum word_type word_type, bool last_one,
typestr = "networkaddr";
break;
case COMMAND:
print_command_json((struct sudo_command *)m->name, indent, last_one);
print_command_json(fp, (struct sudo_command *)m->name, indent, last_one);
debug_return;
case WORD:
switch (word_type) {
@@ -444,10 +446,10 @@ print_member_json(struct member *m, enum word_type word_type, bool last_one,
default:
fatalx("unexpected member type %d", m->type);
}
print_pair_json("{ ", typestr, &value, " }", indent);
print_pair_json(fp, "{ ", typestr, &value, " }", indent);
if (!last_one)
putchar(',');
putchar('\n');
putc(',', fp);
putc('\n', fp);
debug_return;
}
@@ -469,17 +471,19 @@ print_alias_json(void *v1, void *v2)
/* Open the aliases object or close the last entry, then open new one. */
if (closure->count++ == 0) {
printf("%s\n%*s\"%s\": {\n", closure->need_comma ? "," : "",
closure->indent, "", closure->title);
fprintf(closure->fp, "%s\n%*s\"%s\": {\n",
closure->need_comma ? "," : "", closure->indent, "",
closure->title);
closure->indent += 4;
} else {
printf("%*s],\n", closure->indent, "");
fprintf(closure->fp, "%*s],\n", closure->indent, "");
}
printstr_json("\"", a->name, "\": [\n", closure->indent);
printstr_json(closure->fp, "\"", a->name, "\": [\n", closure->indent);
closure->indent += 4;
TAILQ_FOREACH(m, &a->members, entries) {
print_member_json(m, alias_to_word_type(closure->alias_type),
print_member_json(closure->fp, m,
alias_to_word_type(closure->alias_type),
TAILQ_NEXT(m, entries) == NULL, closure->indent);
}
closure->indent -= 4;
@@ -490,7 +494,7 @@ print_alias_json(void *v1, void *v2)
* Print the binding for a Defaults entry of the specified type.
*/
static void
print_binding_json(struct member_list *binding, int type, int indent)
print_binding_json(FILE *fp, struct member_list *binding, int type, int indent)
{
struct member *m;
debug_decl(print_binding_json, SUDO_DEBUG_UTIL)
@@ -498,17 +502,17 @@ print_binding_json(struct member_list *binding, int type, int indent)
if (TAILQ_EMPTY(binding))
debug_return;
printf("%*s\"Binding\": [\n", indent, "");
fprintf(fp, "%*s\"Binding\": [\n", indent, "");
indent += 4;
/* Print each member object in binding. */
TAILQ_FOREACH(m, binding, entries) {
print_member_json(m, defaults_to_word_type(type),
print_member_json(fp, m, defaults_to_word_type(type),
TAILQ_NEXT(m, entries) == NULL, indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
debug_return;
}
@@ -517,13 +521,13 @@ print_binding_json(struct member_list *binding, int type, int indent)
* Print a Defaults list JSON format.
*/
static void
print_defaults_list_json(struct defaults *def, int indent)
print_defaults_list_json(FILE *fp, struct defaults *def, int indent)
{
char savech, *start, *end = def->val;
struct json_value value;
debug_decl(print_defaults_list_json, SUDO_DEBUG_UTIL)
printf("%*s{\n", indent, "");
fprintf(fp, "%*s{\n", indent, "");
indent += 4;
value.type = JSON_STRING;
switch (def->op) {
@@ -541,10 +545,10 @@ print_defaults_list_json(struct defaults *def, int indent)
value.u.string = "unsupported";
break;
}
print_pair_json(NULL, "operation", &value, ",\n", indent);
printstr_json("\"", def->var, "\": [\n", indent);
print_pair_json(fp, NULL, "operation", &value, ",\n", indent);
printstr_json(fp, "\"", def->var, "\": [\n", indent);
indent += 4;
print_indent(indent);
print_indent(fp, indent);
/* Split value into multiple space-separated words. */
do {
/* Remove leading blanks, must have a non-empty string. */
@@ -558,16 +562,16 @@ print_defaults_list_json(struct defaults *def, int indent)
;
savech = *end;
*end = '\0';
print_string_json(start);
print_string_json(fp, start);
if (savech != '\0')
putchar(',');
putc(',', fp);
*end = savech;
} while (*end++ != '\0');
putchar('\n');
putc('\n', fp);
indent -= 4;
printf("%*s]\n", indent, "");
fprintf(fp, "%*s]\n", indent, "");
indent -= 4;
printf("%*s}", indent, "");
fprintf(fp, "%*s}", indent, "");
debug_return;
}
@@ -589,7 +593,7 @@ get_defaults_type(struct defaults *def)
* Export all Defaults in JSON format.
*/
static bool
print_defaults_json(int indent, bool need_comma)
print_defaults_json(FILE *fp, int indent, bool need_comma)
{
struct json_value value;
struct defaults *def, *next;
@@ -599,7 +603,7 @@ print_defaults_json(int indent, bool need_comma)
if (TAILQ_EMPTY(&defaults))
debug_return_bool(need_comma);
printf("%s\n%*s\"Defaults\": [\n", need_comma ? "," : "", indent, "");
fprintf(fp, "%s\n%*s\"Defaults\": [\n", need_comma ? "," : "", indent, "");
TAILQ_FOREACH_SAFE(def, &defaults, entries, next) {
type = get_defaults_type(def);
@@ -610,16 +614,16 @@ print_defaults_json(int indent, bool need_comma)
}
/* Found it, print object container and binding (if any). */
indent = 8;
printf("%*s{\n", indent, "");
indent = 12;
print_binding_json(def->binding, def->type, indent);
indent += 4;
fprintf(fp, "%*s{\n", indent, "");
indent += 4;
print_binding_json(fp, def->binding, def->type, indent);
/* Validation checks. */
/* XXX - validate values in addition to names? */
/* Print options, merging ones with the same binding. */
printf("%*s\"Options\": [\n", indent, "");
fprintf(fp, "%*s\"Options\": [\n", indent, "");
indent += 4;
for (;;) {
next = TAILQ_NEXT(def, entries);
@@ -627,13 +631,13 @@ print_defaults_json(int indent, bool need_comma)
if ((type & T_MASK) == T_FLAG || def->val == NULL) {
value.type = JSON_BOOL;
value.u.boolean = def->op;
print_pair_json("{ ", def->var, &value, " }", indent);
print_pair_json(fp, "{ ", def->var, &value, " }", indent);
} else if ((type & T_MASK) == T_LIST) {
print_defaults_list_json(def, indent);
print_defaults_list_json(fp, def, indent);
} else {
value.type = JSON_STRING;
value.u.string = def->val;
print_pair_json("{ ", def->var, &value, " }", indent);
print_pair_json(fp, "{ ", def->var, &value, " }", indent);
}
if (next == NULL || def->binding != next->binding)
break;
@@ -644,21 +648,21 @@ print_defaults_json(int indent, bool need_comma)
/* XXX - just pass it through as a string anyway? */
break;;
}
fputs(",\n", stdout);
fputs(",\n", fp);
}
putchar('\n');
putc('\n', fp);
indent -= 4;
print_indent(indent);
fputs("]\n", stdout);
print_indent(fp, indent);
fputs("]\n", fp);
indent -= 4;
print_indent(indent);
printf("}%s\n", next != NULL ? "," : "");
print_indent(fp, indent);
fprintf(fp, "}%s\n", next != NULL ? "," : "");
}
/* Close Defaults array; comma (if any) & newline will be printer later. */
indent -= 4;
print_indent(indent);
fputs("]", stdout);
print_indent(fp, indent);
fputs("]", fp);
debug_return_bool(true);
}
@@ -668,12 +672,13 @@ print_defaults_json(int indent, bool need_comma)
* Iterates through the entire aliases tree.
*/
static bool
print_aliases_by_type_json(int alias_type, const char *title, int indent,
bool need_comma)
print_aliases_by_type_json(FILE *fp, int alias_type, const char *title,
int indent, bool need_comma)
{
struct json_alias_closure closure;
debug_decl(print_aliases_by_type_json, SUDO_DEBUG_UTIL)
closure.fp = fp;
closure.indent = indent;
closure.count = 0;
closure.alias_type = alias_type;
@@ -681,11 +686,11 @@ print_aliases_by_type_json(int alias_type, const char *title, int indent,
closure.need_comma = need_comma;
alias_apply(print_alias_json, &closure);
if (closure.count != 0) {
print_indent(closure.indent);
fputs("]\n", stdout);
print_indent(fp, closure.indent);
fputs("]\n", fp);
closure.indent -= 4;
print_indent(closure.indent);
putchar('}');
print_indent(fp, closure.indent);
putc('}', fp);
need_comma = true;
}
@@ -696,17 +701,17 @@ print_aliases_by_type_json(int alias_type, const char *title, int indent,
* Export all aliases in JSON format.
*/
static bool
print_aliases_json(int indent, bool need_comma)
print_aliases_json(FILE *fp, int indent, bool need_comma)
{
debug_decl(print_aliases_json, SUDO_DEBUG_UTIL)
need_comma = print_aliases_by_type_json(USERALIAS, "User_Aliases",
need_comma = print_aliases_by_type_json(fp, USERALIAS, "User_Aliases",
indent, need_comma);
need_comma = print_aliases_by_type_json(RUNASALIAS, "Runas_Aliases",
need_comma = print_aliases_by_type_json(fp, RUNASALIAS, "Runas_Aliases",
indent, need_comma);
need_comma = print_aliases_by_type_json(HOSTALIAS, "Host_Aliases",
need_comma = print_aliases_by_type_json(fp, HOSTALIAS, "Host_Aliases",
indent, need_comma);
need_comma = print_aliases_by_type_json(CMNDALIAS, "Command_Aliases",
need_comma = print_aliases_by_type_json(fp, CMNDALIAS, "Command_Aliases",
indent, need_comma);
debug_return_bool(need_comma);
@@ -734,7 +739,8 @@ print_aliases_json(int indent, bool need_comma)
* merge adjacent entries that are identical in all but the command.
*/
static void
print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp,
int indent)
{
struct cmndspec *next = *nextp;
struct json_value value;
@@ -743,38 +749,38 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
debug_decl(print_cmndspec_json, SUDO_DEBUG_UTIL)
/* Open Cmnd_Spec object. */
printf("%*s{\n", indent, "");
fprintf(fp, "%*s{\n", indent, "");
indent += 4;
/* Print runasuserlist */
if (cs->runasuserlist != NULL) {
printf("%*s\"runasusers\": [\n", indent, "");
fprintf(fp, "%*s\"runasusers\": [\n", indent, "");
indent += 4;
TAILQ_FOREACH(m, cs->runasuserlist, entries) {
print_member_json(m, TYPE_RUNASUSER,
print_member_json(fp, m, TYPE_RUNASUSER,
TAILQ_NEXT(m, entries) == NULL, indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
}
/* Print runasgrouplist */
if (cs->runasgrouplist != NULL) {
printf("%*s\"runasgroups\": [\n", indent, "");
fprintf(fp, "%*s\"runasgroups\": [\n", indent, "");
indent += 4;
TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
print_member_json(m, TYPE_RUNASGROUP,
print_member_json(fp, m, TYPE_RUNASGROUP,
TAILQ_NEXT(m, entries) == NULL, indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
}
/* Print tags */
if (cs->tags.nopasswd != UNSPEC || cs->tags.noexec != UNSPEC ||
cs->tags.setenv != UNSPEC || cs->tags.log_input != UNSPEC ||
cs->tags.log_output != UNSPEC) {
printf("%*s\"Options\": {\n", indent, "");
fprintf(fp, "%*s\"Options\": {\n", indent, "");
indent += 4;
if (cs->tags.nopasswd != UNSPEC) {
value.type = JSON_BOOL;
@@ -782,7 +788,7 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
last_one = cs->tags.noexec == UNSPEC &&
cs->tags.setenv == UNSPEC && cs->tags.log_input == UNSPEC &&
cs->tags.log_output == UNSPEC;
print_pair_json(NULL, "authenticate", &value,
print_pair_json(fp, NULL, "authenticate", &value,
last_one ? "\n" : ",\n", indent);
}
if (cs->tags.noexec != UNSPEC) {
@@ -790,7 +796,7 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
value.u.boolean = cs->tags.noexec;
last_one = cs->tags.setenv == UNSPEC &&
cs->tags.log_input == UNSPEC && cs->tags.log_output == UNSPEC;
print_pair_json(NULL, "noexec", &value,
print_pair_json(fp, NULL, "noexec", &value,
last_one ? "\n" : ",\n", indent);
}
if (cs->tags.setenv != UNSPEC) {
@@ -798,57 +804,57 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
value.u.boolean = cs->tags.setenv;
last_one = cs->tags.log_input == UNSPEC &&
cs->tags.log_output == UNSPEC;
print_pair_json(NULL, "setenv", &value,
print_pair_json(fp, NULL, "setenv", &value,
last_one ? "\n" : ",\n", indent);
}
if (cs->tags.log_input != UNSPEC) {
value.type = JSON_BOOL;
value.u.boolean = cs->tags.log_input;
last_one = cs->tags.log_output == UNSPEC;
print_pair_json(NULL, "log_input", &value,
print_pair_json(fp, NULL, "log_input", &value,
last_one ? "\n" : ",\n", indent);
}
if (cs->tags.log_output != UNSPEC) {
value.type = JSON_BOOL;
value.u.boolean = cs->tags.log_output;
print_pair_json(NULL, "log_output", &value, "\n", indent);
print_pair_json(fp, NULL, "log_output", &value, "\n", indent);
}
indent -= 4;
printf("%*s},\n", indent, "");
fprintf(fp, "%*s},\n", indent, "");
}
#ifdef HAVE_SELINUX
/* Print SELinux role/type */
if (cs->role != NULL && cs->type != NULL) {
printf("%*s\"SELinux_Spec\": [\n", indent, "");
fprintf(fp, "%*s\"SELinux_Spec\": [\n", indent, "");
indent += 4;
value.type = JSON_STRING;
value.u.string = cs->role;
print_pair_json(NULL, "role", &value, ",\n", indent);
print_pair_json(fp, NULL, "role", &value, ",\n", indent);
value.u.string = cs->type;
print_pair_json(NULL, "type", &value, "\n", indent);
print_pair_json(fp, NULL, "type", &value, "\n", indent);
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_PRIV_SET
/* Print Solaris privs/limitprivs */
if (cs->privs != NULL || cs->limitprivs != NULL) {
printf("%*s\"Solaris_Priv_Spec\": [\n", indent, "");
fprintf(fp, "%*s\"Solaris_Priv_Spec\": [\n", indent, "");
indent += 4;
value.type = JSON_STRING;
if (cs->privs != NULL) {
value.u.string = cs->privs;
print_pair_json(NULL, "privs", &value,
print_pair_json(fp, NULL, "privs", &value,
cs->limitprivs != NULL ? ",\n" : "\n", indent);
}
if (cs->limitprivs != NULL) {
value.u.string = cs->limitprivs;
print_pair_json(NULL, "limitprivs", &value, "\n", indent);
print_pair_json(fp, NULL, "limitprivs", &value, "\n", indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
}
#endif /* HAVE_PRIV_SET */
@@ -856,7 +862,7 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
* Merge adjacent commands with matching tags, runas, SELinux
* role/type and Solaris priv settings.
*/
printf("%*s\"Commands\": [\n", indent, "");
fprintf(fp, "%*s\"Commands\": [\n", indent, "");
indent += 4;
for (;;) {
/* Does the next entry differ only in the command itself? */
@@ -871,18 +877,18 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
#endif /* HAVE_SELINUX */
;
print_member_json(cs->cmnd, TYPE_COMMAND, last_one, indent);
print_member_json(fp, cs->cmnd, TYPE_COMMAND, last_one, indent);
if (last_one)
break;
cs = next;
next = TAILQ_NEXT(cs, entries);
}
indent -= 4;
printf("%*s]\n", indent, "");
fprintf(fp, "%*s]\n", indent, "");
/* Close Cmnd_Spec object. */
indent -= 4;
printf("%*s}%s\n", indent, "", TAILQ_NEXT(cs, entries) != NULL ? "," : "");
fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(cs, entries) != NULL ? "," : "");
*nextp = next;
@@ -893,7 +899,7 @@ print_cmndspec_json(struct cmndspec *cs, struct cmndspec **nextp, int indent)
* Print a User_Spec in JSON format at the specified indent level.
*/
static void
print_userspec_json(struct userspec *us, int indent)
print_userspec_json(FILE *fp, struct userspec *us, int indent)
{
struct privilege *priv;
struct member *m;
@@ -907,41 +913,41 @@ print_userspec_json(struct userspec *us, int indent)
*/
TAILQ_FOREACH(priv, &us->privileges, entries) {
/* Open User_Spec object. */
printf("%*s{\n", indent, "");
fprintf(fp, "%*s{\n", indent, "");
indent += 4;
/* Print users list. */
printf("%*s\"User_List\": [\n", indent, "");
fprintf(fp, "%*s\"User_List\": [\n", indent, "");
indent += 4;
TAILQ_FOREACH(m, &us->users, entries) {
print_member_json(m, TYPE_USERNAME,
print_member_json(fp, m, TYPE_USERNAME,
TAILQ_NEXT(m, entries) == NULL, indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
/* Print hosts list. */
printf("%*s\"Host_List\": [\n", indent, "");
fprintf(fp, "%*s\"Host_List\": [\n", indent, "");
indent += 4;
TAILQ_FOREACH(m, &priv->hostlist, entries) {
print_member_json(m, TYPE_HOSTNAME,
print_member_json(fp, m, TYPE_HOSTNAME,
TAILQ_NEXT(m, entries) == NULL, indent);
}
indent -= 4;
printf("%*s],\n", indent, "");
fprintf(fp, "%*s],\n", indent, "");
/* Print commands. */
printf("%*s\"Cmnd_Specs\": [\n", indent, "");
fprintf(fp, "%*s\"Cmnd_Specs\": [\n", indent, "");
indent += 4;
TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
print_cmndspec_json(cs, &next, indent);
print_cmndspec_json(fp, cs, &next, indent);
}
indent -= 4;
printf("%*s]\n", indent, "");
fprintf(fp, "%*s]\n", indent, "");
/* Close User_Spec object. */
indent -= 4;
printf("%*s}%s\n", indent, "", TAILQ_NEXT(priv, entries) != NULL ||
fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(priv, entries) != NULL ||
TAILQ_NEXT(us, entries) != NULL ? "," : "");
}
@@ -949,7 +955,7 @@ print_userspec_json(struct userspec *us, int indent)
}
static bool
print_userspecs_json(int indent, bool need_comma)
print_userspecs_json(FILE *fp, int indent, bool need_comma)
{
struct userspec *us;
debug_decl(print_userspecs_json, SUDO_DEBUG_UTIL)
@@ -957,13 +963,13 @@ print_userspecs_json(int indent, bool need_comma)
if (TAILQ_EMPTY(&userspecs))
debug_return_bool(need_comma);
printf("%s\n%*s\"User_Specs\": [\n", need_comma ? "," : "", indent, "");
fprintf(fp, "%s\n%*s\"User_Specs\": [\n", need_comma ? "," : "", indent, "");
indent += 4;
TAILQ_FOREACH(us, &userspecs, entries) {
print_userspec_json(us, indent);
print_userspec_json(fp, us, indent);
}
indent -= 4;
printf("%*s]", indent, "");
fprintf(fp, "%*s]", indent, "");
debug_return_bool(true);
}
@@ -971,13 +977,14 @@ print_userspecs_json(int indent, bool need_comma)
/*
* Export the parsed sudoers file in JSON format.
* XXX - ignores strict flag and doesn't pass through quiet flag
* XXX - pass indent=4 to other json functions
*/
bool
export_sudoers(char *sudoers_path, bool quiet, bool strict)
export_sudoers(const char *sudoers_path, const char *export_path,
bool quiet, bool strict)
{
bool ok = false, need_comma = false;
const int indent = 4;
FILE *export_fp;
debug_decl(export_sudoers, SUDO_DEBUG_UTIL)
if (strcmp(sudoers_path, "-") == 0) {
@@ -988,6 +995,14 @@ export_sudoers(char *sudoers_path, bool quiet, bool strict)
warning(U_("unable to open %s"), sudoers_path);
goto done;
}
if (strcmp(export_path, "-") == 0) {
export_fp = stdout;
export_path = "stdout";
} else if ((export_fp = fopen(export_path, "w")) == NULL) {
if (!quiet)
warning(U_("unable to open %s"), export_path);
goto done;
}
init_parser(sudoers_path, quiet);
if (sudoersparse() && !parse_error) {
if (!quiet)
@@ -1000,28 +1015,29 @@ export_sudoers(char *sudoers_path, bool quiet, bool strict)
if (parse_error) {
if (!quiet) {
if (errorlineno != -1)
printf(_("parse error in %s near line %d\n"),
warningx(_("parse error in %s near line %d\n"),
errorfile, errorlineno);
else if (errorfile != NULL)
printf(_("parse error in %s\n"), errorfile);
warningx(_("parse error in %s\n"), errorfile);
}
goto done;
}
/* Open JSON output. */
putchar('{');
putc('{', export_fp);
/* Dump Defaults in JSON format. */
need_comma = print_defaults_json(indent, need_comma);
need_comma = print_defaults_json(export_fp, indent, need_comma);
/* Dump Aliases in JSON format. */
need_comma = print_aliases_json(indent, need_comma);
need_comma = print_aliases_json(export_fp, indent, need_comma);
/* Dump User_Specs in JSON format. */
print_userspecs_json(indent, need_comma);
print_userspecs_json(export_fp, indent, need_comma);
/* Close JSON output. */
puts("\n}");
fputs("\n}\n", export_fp);
fclose(export_fp);
done:
debug_return_bool(ok);