diff --git a/include/sudo_json.h b/include/sudo_json.h index 0988fbda9..6da18f67f 100644 --- a/include/sudo_json.h +++ b/include/sudo_json.h @@ -68,3 +68,6 @@ __dso_public bool sudo_json_close_array_v1(struct json_container *json); __dso_public bool sudo_json_add_value_v1(struct json_container *json, const char *name, struct json_value *value); #define sudo_json_add_value(_a, _b, _c) sudo_json_add_value_v1((_a), (_b), (_c)) + +__dso_public bool sudo_json_add_value_as_object_v1(struct json_container *json, const char *name, struct json_value *value); +#define sudo_json_add_value_as_object(_a, _b, _c) sudo_json_add_value_as_object_v1((_a), (_b), (_c)) diff --git a/lib/util/json.c b/lib/util/json.c index 373848ef0..395746bba 100644 --- a/lib/util/json.c +++ b/lib/util/json.c @@ -125,9 +125,11 @@ sudo_json_open_object_v1(struct json_container *json, const char *name) print_indent(json->fp, json->indent_level); - json_print_string(json, name); - putc(':', json->fp); - putc(' ', json->fp); + if (name != NULL) { + json_print_string(json, name); + putc(':', json->fp); + putc(' ', json->fp); + } putc('{', json->fp); json->indent_level += json->indent_increment; @@ -186,8 +188,8 @@ sudo_json_close_array_v1(struct json_container *json) } bool -sudo_json_add_value_v1(struct json_container *json, const char *name, - struct json_value *value) +sudo_json_add_value_int(struct json_container *json, const char *name, + struct json_value *value, bool as_object) { unsigned int i; debug_decl(sudo_json_add_value, SUDO_DEBUG_UTIL); @@ -200,10 +202,17 @@ sudo_json_add_value_v1(struct json_container *json, const char *name, print_indent(json->fp, json->indent_level); + if (as_object) { + putc('{', json->fp); + putc(' ', json->fp); + } + /* name */ - json_print_string(json, name); - putc(':', json->fp); - putc(' ', json->fp); + if (name != NULL) { + json_print_string(json, name); + putc(':', json->fp); + putc(' ', json->fp); + } /* value */ switch (value->type) { @@ -254,5 +263,24 @@ sudo_json_add_value_v1(struct json_container *json, const char *name, break; } + if (as_object) { + putc(' ', json->fp); + putc('}', json->fp); + } + debug_return_bool(true); } + +bool +sudo_json_add_value_v1(struct json_container *json, const char *name, + struct json_value *value) +{ + return sudo_json_add_value_int(json, name, value, false); +} + +bool +sudo_json_add_value_as_object_v1(struct json_container *json, const char *name, + struct json_value *value) +{ + return sudo_json_add_value_int(json, name, value, true); +} diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index 392ccace5..2da9aa4e4 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -3,8 +3,8 @@ sudo_conf_askpass_path_v1 sudo_conf_clear_paths_v1 sudo_conf_debug_files_v1 sudo_conf_debugging_v1 -sudo_conf_devsearch_path_v1 sudo_conf_developer_mode_v1 +sudo_conf_devsearch_path_v1 sudo_conf_disable_coredump_v1 sudo_conf_group_source_v1 sudo_conf_max_groups_v1 @@ -32,12 +32,12 @@ sudo_debug_fork_v1 sudo_debug_get_active_instance_v1 sudo_debug_get_fds_v1 sudo_debug_get_instance_v1 +sudo_debug_needed_v1 sudo_debug_printf2_v1 sudo_debug_register_v1 sudo_debug_set_active_instance_v1 sudo_debug_update_fd_v1 sudo_debug_vprintf2_v1 -sudo_debug_needed_v1 sudo_debug_write2_v1 sudo_digest_alloc_v1 sudo_digest_final_v1 @@ -63,10 +63,10 @@ sudo_ev_get_timeleft_v1 sudo_ev_get_timeleft_v2 sudo_ev_got_break_v1 sudo_ev_got_exit_v1 -sudo_ev_loop_v1 sudo_ev_loopbreak_v1 sudo_ev_loopcontinue_v1 sudo_ev_loopexit_v1 +sudo_ev_loop_v1 sudo_ev_pending_v1 sudo_ev_set_v1 sudo_fatal_callback_deregister_v1 @@ -77,12 +77,13 @@ sudo_gai_fatal_nodebug_v1 sudo_gai_vfatal_nodebug_v1 sudo_gai_vwarn_nodebug_v1 sudo_gai_warn_nodebug_v1 -sudo_get_ttysize_v1 sudo_getgrouplist2_v1 sudo_gethostname_v1 sudo_gettime_awake_v1 sudo_gettime_mono_v1 sudo_gettime_real_v1 +sudo_get_ttysize_v1 +sudo_json_add_value_as_object_v1 sudo_json_add_value_v1 sudo_json_close_array_v1 sudo_json_close_object_v1 diff --git a/plugins/sudoers/cvtsudoers_json.c b/plugins/sudoers/cvtsudoers_json.c index 9485d45d9..32d48964b 100644 --- a/plugins/sudoers/cvtsudoers_json.c +++ b/plugins/sudoers/cvtsudoers_json.c @@ -38,46 +38,18 @@ #include #include "sudoers.h" +#include "sudo_json.h" #include "cvtsudoers.h" #include -/* - * JSON values may be of the following types. - */ -enum json_value_type { - JSON_STRING, - JSON_ID, - JSON_NUMBER, - JSON_OBJECT, - JSON_ARRAY, - JSON_BOOL, - JSON_NULL -}; - -/* - * JSON value suitable for printing. - * Note: this does not support object or array values. - */ -struct json_value { - enum json_value_type type; - union { - char *string; - int number; - id_t id; - bool boolean; - } u; -}; - /* * Closure used to store state when iterating over all aliases. */ struct json_alias_closure { - FILE *fp; + struct json_container *json; const char *title; unsigned int count; int alias_type; - int indent; - bool need_comma; }; /* @@ -92,201 +64,55 @@ enum word_type { }; /* - * Print "indent" number of blank characters. + * Print sudo command member in JSON format, with correct indentation. */ static void -print_indent(FILE *fp, int indent) -{ - while (indent--) - putc(' ', fp); -} - -/* - * Print a JSON string, escaping special characters. - * Does not support unicode escapes. - */ -static void -print_string_json_unquoted(FILE *fp, const char *str) -{ - char ch; - - while ((ch = *str++) != '\0') { - switch (ch) { - case '"': - case '\\': - putc('\\', fp); - break; - case '\b': - ch = 'b'; - putc('\\', fp); - break; - case '\f': - ch = 'f'; - putc('\\', fp); - break; - case '\n': - ch = 'n'; - putc('\\', fp); - break; - case '\r': - ch = 'r'; - putc('\\', fp); - break; - case '\t': - ch = 't'; - putc('\\', fp); - break; - } - putc(ch, fp); - } -} - -/* - * Print a quoted JSON string, escaping special characters. - * Does not support unicode escapes. - */ -static void -print_string_json(FILE *fp, const char *str) -{ - 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(FILE *fp, const char *pre, const char *name, - const struct json_value *value, const char *post, int indent) -{ - debug_decl(print_pair_json, SUDOERS_DEBUG_UTIL); - - print_indent(fp, indent); - - /* prefix */ - if (pre != NULL) - fputs(pre, fp); - - /* name */ - print_string_json(fp, name); - putc(':', fp); - putc(' ', fp); - - /* value */ - switch (value->type) { - case JSON_STRING: - print_string_json(fp, value->u.string); - break; - case JSON_ID: - fprintf(fp, "%u", (unsigned int)value->u.id); - break; - case JSON_NUMBER: - fprintf(fp, "%d", value->u.number); - break; - case JSON_NULL: - fputs("null", fp); - break; - case JSON_BOOL: - fputs(value->u.boolean ? "true" : "false", fp); - break; - case JSON_OBJECT: - sudo_fatalx("internal error: can't print JSON_OBJECT"); - break; - case JSON_ARRAY: - sudo_fatalx("internal error: can't print JSON_ARRAY"); - break; - } - - /* postfix */ - if (post != NULL) - fputs(post, fp); - - debug_return; -} - -/* - * 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(FILE *fp, const char *pre, const char *str, const char *post, - int indent) -{ - debug_decl(printstr_json, SUDOERS_DEBUG_UTIL); - - print_indent(fp, indent); - if (pre != NULL) - fputs(pre, fp); - if (str != NULL) { - print_string_json_unquoted(fp, str); - } - if (post != NULL) - fputs(post, fp); - debug_return; -} - -/* - * Print sudo command member in JSON format, with specified indentation. - * If last_one is false, a comma will be printed before the newline - * that closes the object. - */ -static void -print_command_json(FILE *fp, const char *name, int type, bool negated, int indent, bool last_one) +print_command_json(struct json_container *json, const char *name, bool negated) { struct sudo_command *c = (struct sudo_command *)name; struct json_value value; + char *cmnd = c->cmnd; const char *digest_name; debug_decl(print_command_json, SUDOERS_DEBUG_UTIL); - printstr_json(fp, "{", NULL, NULL, indent); - if (negated || c->digest != NULL) { - putc('\n', fp); - indent += 4; - } else { - putc(' ', fp); - indent = 0; - } - /* Print command with optional command line args. */ if (c->args != NULL) { - printstr_json(fp, "\"", "command", "\": ", indent); - printstr_json(fp, "\"", c->cmnd, " ", 0); - printstr_json(fp, NULL, c->args, "\"", 0); + if (asprintf(&cmnd, "%s %s", c->cmnd, c->args) == -1) { + sudo_fatalx(U_("%s: %s"), __func__, + U_("unable to allocate memory")); + } + } + value.type = JSON_STRING; + value.u.string = cmnd; + + if (!negated && c->digest == NULL) { + /* Print as { "command": "command and args" } */ + sudo_json_add_value_as_object(json, "command", &value); } else { - value.type = JSON_STRING; - value.u.string = c->cmnd; - print_pair_json(fp, NULL, "command", &value, NULL, indent); + /* Print as multi-line object. */ + sudo_json_open_object(json, NULL); + sudo_json_add_value(json, "command", &value); + + /* Optional digest. */ + if (c->digest != NULL) { + digest_name = digest_type_to_name(c->digest->digest_type); + value.type = JSON_STRING; + value.u.string = c->digest->digest_str; + sudo_json_add_value(json, digest_name, &value); + } + + /* Command may be negated. */ + if (negated) { + value.type = JSON_BOOL; + value.u.boolean = true; + sudo_json_add_value(json, "negated", &value); + } + + sudo_json_close_object(json); } - /* Optional digest. */ - if (c->digest != NULL) { - fputs(",\n", fp); - digest_name = digest_type_to_name(c->digest->digest_type); - value.type = JSON_STRING; - value.u.string = c->digest->digest_str; - print_pair_json(fp, NULL, digest_name, &value, NULL, indent); - } - - /* Command may be negated. */ - if (negated) { - fputs(",\n", fp); - value.type = JSON_BOOL; - value.u.boolean = true; - print_pair_json(fp, NULL, "negated", &value, NULL, indent); - } - - if (indent != 0) { - indent -= 4; - putc('\n', fp); - print_indent(fp, indent); - } else { - putc(' ', fp); - } - putc('}', fp); - if (!last_one) - putc(',', fp); - putc('\n', fp); + if (cmnd != c->cmnd) + free(cmnd); debug_return; } @@ -332,14 +158,12 @@ defaults_to_word_type(int defaults_type) } /* - * Print struct member in JSON format, with specified indentation. - * If last_one is false, a comma will be printed before the newline - * that closes the object. + * Print struct member in JSON format, with correct indentation. */ static void -print_member_json_int(FILE *fp, struct sudoers_parse_tree *parse_tree, - char *name, int type, bool negated, enum word_type word_type, - bool last_one, int indent, bool expand_aliases) +print_member_json_int(struct json_container *json, + struct sudoers_parse_tree *parse_tree, char *name, int type, bool negated, + enum word_type word_type, bool expand_aliases) { struct json_value value; const char *typestr = NULL; @@ -405,7 +229,7 @@ print_member_json_int(FILE *fp, struct sudoers_parse_tree *parse_tree, typestr = "networkaddr"; break; case COMMAND: - print_command_json(fp, name, type, negated, indent, last_one); + print_command_json(json, name, negated); debug_return; case ALL: case MYSELF: @@ -485,44 +309,35 @@ print_member_json_int(FILE *fp, struct sudoers_parse_tree *parse_tree, /* Print each member of the alias. */ if ((a = alias_get(parse_tree, value.u.string, alias_type)) != NULL) { TAILQ_FOREACH(m, &a->members, entries) { - print_member_json_int(fp, parse_tree, m->name, m->type, + print_member_json_int(json, parse_tree, m->name, m->type, negated ? !m->negated : m->negated, - alias_to_word_type(alias_type), - last_one && TAILQ_NEXT(m, entries) == NULL, indent, true); + alias_to_word_type(alias_type), true); } alias_put(a); } } else { if (negated) { - print_indent(fp, indent); - fputs("{\n", fp); - indent += 4; - print_pair_json(fp, NULL, typestr, &value, ",\n", indent); + sudo_json_open_object(json, NULL); + sudo_json_add_value(json, typestr, &value); value.type = JSON_BOOL; value.u.boolean = true; - print_pair_json(fp, NULL, "negated", &value, "\n", indent); - indent -= 4; - print_indent(fp, indent); - putc('}', fp); + sudo_json_add_value(json, "negated", &value); + sudo_json_close_object(json); } else { - print_pair_json(fp, "{ ", typestr, &value, " }", indent); + sudo_json_add_value_as_object(json, typestr, &value); } - - if (!last_one) - putc(',', fp); - putc('\n', fp); } debug_return; } static void -print_member_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - struct member *m, enum word_type word_type, bool last_one, - int indent, bool expand_aliases) +print_member_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, struct member *m, + enum word_type word_type, bool expand_aliases) { - print_member_json_int(fp, parse_tree, m->name, m->type, m->negated, - word_type, last_one, indent, expand_aliases); + print_member_json_int(json, parse_tree, m->name, m->type, m->negated, + word_type, expand_aliases); } /* @@ -541,22 +356,16 @@ print_alias_json(struct sudoers_parse_tree *parse_tree, struct alias *a, void *v /* Open the aliases object or close the last entry, then open new one. */ if (closure->count++ == 0) { - fprintf(closure->fp, "%s\n%*s\"%s\": {\n", - closure->need_comma ? "," : "", closure->indent, "", - closure->title); - closure->indent += 4; + sudo_json_open_object(closure->json, closure->title); } else { - fprintf(closure->fp, "%*s],\n", closure->indent, ""); + sudo_json_close_array(closure->json); } - printstr_json(closure->fp, "\"", a->name, "\": [\n", closure->indent); + sudo_json_open_array(closure->json, a->name); - closure->indent += 4; TAILQ_FOREACH(m, &a->members, entries) { - print_member_json(closure->fp, parse_tree, m, - alias_to_word_type(closure->alias_type), - TAILQ_NEXT(m, entries) == NULL, closure->indent, false); + print_member_json(closure->json, parse_tree, m, + alias_to_word_type(closure->alias_type), false); } - closure->indent -= 4; debug_return_int(0); } @@ -564,8 +373,9 @@ print_alias_json(struct sudoers_parse_tree *parse_tree, struct alias *a, void *v * Print the binding for a Defaults entry of the specified type. */ static void -print_binding_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - struct member_list *binding, int type, int indent, bool expand_aliases) +print_binding_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, struct member_list *binding, + int type, bool expand_aliases) { struct member *m; debug_decl(print_binding_json, SUDOERS_DEBUG_UTIL); @@ -573,17 +383,13 @@ print_binding_json(FILE *fp, struct sudoers_parse_tree *parse_tree, if (TAILQ_EMPTY(binding)) debug_return; - fprintf(fp, "%*s\"Binding\": [\n", indent, ""); - indent += 4; - /* Print each member object in binding. */ + sudo_json_open_array(json, "Binding"); TAILQ_FOREACH(m, binding, entries) { - print_member_json(fp, parse_tree, m, defaults_to_word_type(type), - TAILQ_NEXT(m, entries) == NULL, indent, expand_aliases); + print_member_json(json, parse_tree, m, defaults_to_word_type(type), + expand_aliases); } - - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); debug_return; } @@ -592,14 +398,13 @@ print_binding_json(FILE *fp, struct sudoers_parse_tree *parse_tree, * Print a Defaults list JSON format. */ static void -print_defaults_list_json(FILE *fp, struct defaults *def, int indent) +print_defaults_list_json(struct json_container *json, struct defaults *def) { char savech, *start, *end = def->val; struct json_value value; debug_decl(print_defaults_list_json, SUDOERS_DEBUG_UTIL); - fprintf(fp, "%*s{\n", indent, ""); - indent += 4; + sudo_json_open_object(json, NULL); value.type = JSON_STRING; switch (def->op) { case '+': @@ -616,10 +421,8 @@ print_defaults_list_json(FILE *fp, struct defaults *def, int indent) value.u.string = "unsupported"; break; } - print_pair_json(fp, NULL, "operation", &value, ",\n", indent); - printstr_json(fp, "\"", def->var, "\": [\n", indent); - indent += 4; - print_indent(fp, indent); + sudo_json_add_value(json, "operation", &value); + sudo_json_open_array(json, def->var); /* Split value into multiple space-separated words. */ do { /* Remove leading blanks, must have a non-empty string. */ @@ -633,16 +436,13 @@ print_defaults_list_json(FILE *fp, struct defaults *def, int indent) continue; savech = *end; *end = '\0'; - print_string_json(fp, start); - if (savech != '\0') - putc(',', fp); + value.type = JSON_STRING; + value.u.string = start; + sudo_json_add_value(json, NULL, &value); *end = savech; } while (*end++ != '\0'); - putc('\n', fp); - indent -= 4; - fprintf(fp, "%*s]\n", indent, ""); - indent -= 4; - fprintf(fp, "%*s}", indent, ""); + sudo_json_close_array(json); + sudo_json_close_object(json); debug_return; } @@ -663,9 +463,9 @@ get_defaults_type(struct defaults *def) /* * Export all Defaults in JSON format. */ -static bool -print_defaults_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - int indent, bool expand_aliases, bool need_comma) +static void +print_defaults_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, bool expand_aliases) { struct json_value value; struct defaults *def, *next; @@ -673,10 +473,9 @@ print_defaults_json(FILE *fp, struct sudoers_parse_tree *parse_tree, debug_decl(print_defaults_json, SUDOERS_DEBUG_UTIL); if (TAILQ_EMPTY(&parse_tree->defaults)) - debug_return_bool(need_comma); + debug_return; - fprintf(fp, "%s\n%*s\"Defaults\": [\n", need_comma ? "," : "", indent, ""); - indent += 4; + sudo_json_open_array(json, "Defaults"); TAILQ_FOREACH_SAFE(def, &parse_tree->defaults, entries, next) { type = get_defaults_type(def); @@ -687,30 +486,28 @@ print_defaults_json(FILE *fp, struct sudoers_parse_tree *parse_tree, } /* Found it, print object container and binding (if any). */ - fprintf(fp, "%*s{\n", indent, ""); - indent += 4; - print_binding_json(fp, parse_tree, def->binding, def->type, - indent, expand_aliases); + sudo_json_open_object(json, NULL); + print_binding_json(json, parse_tree, def->binding, def->type, + expand_aliases); /* Validation checks. */ /* XXX - validate values in addition to names? */ /* Print options, merging ones with the same binding. */ - fprintf(fp, "%*s\"Options\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "Options"); for (;;) { next = TAILQ_NEXT(def, entries); /* XXX - need to update cur too */ if ((type & T_MASK) == T_FLAG || def->val == NULL) { value.type = JSON_BOOL; value.u.boolean = def->op; - print_pair_json(fp, "{ ", def->var, &value, " }", indent); + sudo_json_add_value_as_object(json, def->var, &value); } else if ((type & T_MASK) == T_LIST) { - print_defaults_list_json(fp, def, indent); + print_defaults_list_json(json, def); } else { value.type = JSON_STRING; value.u.string = def->val; - print_pair_json(fp, "{ ", def->var, &value, " }", indent); + sudo_json_add_value_as_object(json, def->var, &value); } if (next == NULL || def->binding != next->binding) break; @@ -721,137 +518,125 @@ print_defaults_json(FILE *fp, struct sudoers_parse_tree *parse_tree, /* XXX - just pass it through as a string anyway? */ break; } - fputs(",\n", fp); } - putc('\n', fp); - indent -= 4; - print_indent(fp, indent); - fputs("]\n", fp); - indent -= 4; - print_indent(fp, indent); - fprintf(fp, "}%s\n", next != NULL ? "," : ""); + sudo_json_close_array(json); + sudo_json_close_object(json); } /* Close Defaults array; comma (if any) & newline will be printer later. */ - indent -= 4; - print_indent(fp, indent); - fputs("]", fp); + sudo_json_close_array(json); - debug_return_bool(true); + debug_return; } /* * Export all aliases of the specified type in JSON format. * Iterates through the entire aliases tree. */ -static bool -print_aliases_by_type_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - int alias_type, const char *title, int indent, bool need_comma) +static void +print_aliases_by_type_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, int alias_type, const char *title) { struct json_alias_closure closure; debug_decl(print_aliases_by_type_json, SUDOERS_DEBUG_UTIL); - closure.fp = fp; - closure.indent = indent; + closure.json = json; closure.count = 0; closure.alias_type = alias_type; closure.title = title; - closure.need_comma = need_comma; alias_apply(parse_tree, print_alias_json, &closure); if (closure.count != 0) { - print_indent(fp, closure.indent); - fputs("]\n", fp); - closure.indent -= 4; - print_indent(fp, closure.indent); - putc('}', fp); - need_comma = true; + sudo_json_close_array(json); + sudo_json_close_object(json); } - debug_return_bool(need_comma); + debug_return; } /* * Export all aliases in JSON format. */ -static bool -print_aliases_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - int indent, bool need_comma) +static void +print_aliases_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree) { debug_decl(print_aliases_json, SUDOERS_DEBUG_UTIL); - need_comma = print_aliases_by_type_json(fp, parse_tree, USERALIAS, - "User_Aliases", indent, need_comma); - need_comma = print_aliases_by_type_json(fp, parse_tree, RUNASALIAS, - "Runas_Aliases", indent, need_comma); - need_comma = print_aliases_by_type_json(fp, parse_tree, HOSTALIAS, - "Host_Aliases", indent, need_comma); - need_comma = print_aliases_by_type_json(fp, parse_tree, CMNDALIAS, - "Command_Aliases", indent, need_comma); + print_aliases_by_type_json(json, parse_tree, USERALIAS, "User_Aliases"); + print_aliases_by_type_json(json, parse_tree, RUNASALIAS, "Runas_Aliases"); + print_aliases_by_type_json(json, parse_tree, HOSTALIAS, "Host_Aliases"); + print_aliases_by_type_json(json, parse_tree, CMNDALIAS, "Command_Aliases"); - debug_return_bool(need_comma); + debug_return; +} + +/* Does the next entry differ only in the command itself? */ +static bool +cmndspec_continues(struct cmndspec *cs, struct cmndspec *next) +{ + bool ret = next != NULL && + !RUNAS_CHANGED(cs, next) && !TAGS_CHANGED(cs->tags, next->tags) +#ifdef HAVE_PRIV_SET + && cs->privs == next->privs && cs->limitprivs == next->limitprivs +#endif /* HAVE_PRIV_SET */ +#ifdef HAVE_SELINUX + && cs->role == next->role && cs->type == next->type +#endif /* HAVE_SELINUX */ + ; + return ret; } /* - * Print a Cmnd_Spec in JSON format at the specified indent level. + * Print a Cmnd_Spec in JSON format at the correct indent level. * A pointer to the next Cmnd_Spec is passed in to make it possible to * merge adjacent entries that are identical in all but the command. */ static void -print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - struct cmndspec *cs, struct cmndspec **nextp, - struct defaults_list *options, bool expand_aliases, int indent) +print_cmndspec_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, struct cmndspec *cs, + struct cmndspec **nextp, struct defaults_list *options, bool expand_aliases) { struct cmndspec *next = *nextp; struct json_value value; struct defaults *def; struct member *m; struct tm *tp; - bool last_one; char timebuf[sizeof("20120727121554Z")]; debug_decl(print_cmndspec_json, SUDOERS_DEBUG_UTIL); /* Open Cmnd_Spec object. */ - fprintf(fp, "%*s{\n", indent, ""); - indent += 4; + sudo_json_open_object(json, NULL); /* Print runasuserlist */ if (cs->runasuserlist != NULL) { - fprintf(fp, "%*s\"runasusers\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "runasusers"); TAILQ_FOREACH(m, cs->runasuserlist, entries) { - print_member_json(fp, parse_tree, m, TYPE_RUNASUSER, - TAILQ_NEXT(m, entries) == NULL, indent, expand_aliases); + print_member_json(json, parse_tree, m, TYPE_RUNASUSER, + expand_aliases); } - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); } /* Print runasgrouplist */ if (cs->runasgrouplist != NULL) { - fprintf(fp, "%*s\"runasgroups\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "runasgroups"); TAILQ_FOREACH(m, cs->runasgrouplist, entries) { - print_member_json(fp, parse_tree, m, TYPE_RUNASGROUP, - TAILQ_NEXT(m, entries) == NULL, indent, expand_aliases); + print_member_json(json, parse_tree, m, TYPE_RUNASGROUP, + expand_aliases); } - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); } /* Print options and tags */ if (cs->timeout > 0 || cs->notbefore != UNSPEC || cs->notafter != UNSPEC || TAGS_SET(cs->tags) || !TAILQ_EMPTY(options)) { struct cmndtag tag = cs->tags; - const char *prefix = "\n"; - fprintf(fp, "%*s\"Options\": [", indent, ""); - indent += 4; + sudo_json_open_array(json, "Options"); if (cs->timeout > 0) { value.type = JSON_NUMBER; value.u.number = cs->timeout; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "command_timeout", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "command_timeout", &value); } if (cs->notbefore != UNSPEC) { if ((tp = gmtime(&cs->notbefore)) == NULL) { @@ -862,9 +647,7 @@ print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, } else { value.type = JSON_STRING; value.u.string = timebuf; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "notbefore", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "notbefore", &value); } } } @@ -877,60 +660,44 @@ print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, } else { value.type = JSON_STRING; value.u.string = timebuf; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "notafter", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "notafter", &value); } } } if (tag.nopasswd != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = !tag.nopasswd; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "authenticate", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "authenticate", &value); } if (tag.noexec != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.noexec; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "noexec", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "noexec", &value); } if (tag.send_mail != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.send_mail; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "send_mail", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "send_mail", &value); } if (tag.setenv != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.setenv; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "setenv", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "setenv", &value); } if (tag.follow != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.follow; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "sudoedit_follow", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "sudoedit_follow", &value); } if (tag.log_input != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.log_input; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "log_input", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "log_input", &value); } if (tag.log_output != UNSPEC) { value.type = JSON_BOOL; value.u.boolean = tag.log_output; - fputs(prefix, fp); - print_pair_json(fp, "{ ", "log_output", &value, " }", indent); - prefix = ",\n"; + sudo_json_add_value_as_object(json, "log_output", &value); } TAILQ_FOREACH(def, options, entries) { int type = get_defaults_type(def); @@ -939,57 +706,48 @@ print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, /* XXX - just pass it through as a string anyway? */ continue; } - fputs(prefix, fp); if ((type & T_MASK) == T_FLAG || def->val == NULL) { value.type = JSON_BOOL; value.u.boolean = def->op; - print_pair_json(fp, "{ ", def->var, &value, " }", indent); + sudo_json_add_value_as_object(json, def->var, &value); } else if ((type & T_MASK) == T_LIST) { - print_defaults_list_json(fp, def, indent); + print_defaults_list_json(json, def); } else { value.type = JSON_STRING; value.u.string = def->val; - print_pair_json(fp, "{ ", def->var, &value, " }", indent); + sudo_json_add_value_as_object(json, def->var, &value); } - prefix = ",\n"; } - putc('\n', fp); - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); } #ifdef HAVE_SELINUX /* Print SELinux role/type */ if (cs->role != NULL && cs->type != NULL) { - fprintf(fp, "%*s\"SELinux_Spec\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "SELinux_Spec"); value.type = JSON_STRING; value.u.string = cs->role; - print_pair_json(fp, NULL, "role", &value, ",\n", indent); + sudo_json_add_value(json, "role", &value); value.u.string = cs->type; - print_pair_json(fp, NULL, "type", &value, "\n", indent); - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_add_value(json, "type", &value); + sudo_json_close_array(json); } #endif /* HAVE_SELINUX */ #ifdef HAVE_PRIV_SET /* Print Solaris privs/limitprivs */ if (cs->privs != NULL || cs->limitprivs != NULL) { - fprintf(fp, "%*s\"Solaris_Priv_Spec\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "Solaris_Priv_Spec"); value.type = JSON_STRING; if (cs->privs != NULL) { value.u.string = cs->privs; - print_pair_json(fp, NULL, "privs", &value, - cs->limitprivs != NULL ? ",\n" : "\n", indent); + sudo_json_add_value(json, "privs", &value); } if (cs->limitprivs != NULL) { value.u.string = cs->limitprivs; - print_pair_json(fp, NULL, "limitprivs", &value, "\n", indent); + sudo_json_add_value(json, "limitprivs", &value); } - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); } #endif /* HAVE_PRIV_SET */ @@ -997,34 +755,20 @@ print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, * Merge adjacent commands with matching tags, runas, SELinux * role/type and Solaris priv settings. */ - fprintf(fp, "%*s\"Commands\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "Commands"); for (;;) { + print_member_json(json, parse_tree, cs->cmnd, TYPE_COMMAND, + expand_aliases); /* Does the next entry differ only in the command itself? */ - /* XXX - move into a function that returns bool */ - last_one = next == NULL || - RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags) -#ifdef HAVE_PRIV_SET - || cs->privs != next->privs || cs->limitprivs != next->limitprivs -#endif /* HAVE_PRIV_SET */ -#ifdef HAVE_SELINUX - || cs->role != next->role || cs->type != next->type -#endif /* HAVE_SELINUX */ - ; - - print_member_json(fp, parse_tree, cs->cmnd, TYPE_COMMAND, - last_one, indent, expand_aliases); - if (last_one) + if (!cmndspec_continues(cs, next)) break; cs = next; next = TAILQ_NEXT(cs, entries); } - indent -= 4; - fprintf(fp, "%*s]\n", indent, ""); + sudo_json_close_array(json); /* Close Cmnd_Spec object. */ - indent -= 4; - fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(cs, entries) != NULL ? "," : ""); + sudo_json_close_object(json); *nextp = next; @@ -1032,11 +776,12 @@ print_cmndspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, } /* - * Print a User_Spec in JSON format at the specified indent level. + * Print a User_Spec in JSON format at the correct indent level. */ static void -print_userspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - struct userspec *us, int indent, bool expand_aliases) +print_userspec_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, struct userspec *us, + bool expand_aliases) { struct privilege *priv; struct member *m; @@ -1050,67 +795,56 @@ print_userspec_json(FILE *fp, struct sudoers_parse_tree *parse_tree, */ TAILQ_FOREACH(priv, &us->privileges, entries) { /* Open User_Spec object. */ - fprintf(fp, "%*s{\n", indent, ""); - indent += 4; + sudo_json_open_object(json, NULL); /* Print users list. */ - fprintf(fp, "%*s\"User_List\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "User_List"); TAILQ_FOREACH(m, &us->users, entries) { - print_member_json(fp, parse_tree, m, TYPE_USERNAME, - TAILQ_NEXT(m, entries) == NULL, indent, expand_aliases); + print_member_json(json, parse_tree, m, TYPE_USERNAME, + expand_aliases); } - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); /* Print hosts list. */ - fprintf(fp, "%*s\"Host_List\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "Host_List"); TAILQ_FOREACH(m, &priv->hostlist, entries) { - print_member_json(fp, parse_tree, m, TYPE_HOSTNAME, - TAILQ_NEXT(m, entries) == NULL, indent, expand_aliases); + print_member_json(json, parse_tree, m, TYPE_HOSTNAME, + expand_aliases); } - indent -= 4; - fprintf(fp, "%*s],\n", indent, ""); + sudo_json_close_array(json); /* Print commands. */ - fprintf(fp, "%*s\"Cmnd_Specs\": [\n", indent, ""); - indent += 4; + sudo_json_open_array(json, "Cmnd_Specs"); TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) { - print_cmndspec_json(fp, parse_tree, cs, &next, &priv->defaults, - expand_aliases, indent); + print_cmndspec_json(json, parse_tree, cs, &next, &priv->defaults, + expand_aliases); } - indent -= 4; - fprintf(fp, "%*s]\n", indent, ""); + sudo_json_close_array(json); /* Close User_Spec object. */ - indent -= 4; - fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(priv, entries) != NULL || - TAILQ_NEXT(us, entries) != NULL ? "," : ""); + sudo_json_close_object(json); } debug_return; } -static bool -print_userspecs_json(FILE *fp, struct sudoers_parse_tree *parse_tree, - int indent, bool expand_aliases, bool need_comma) +static void +print_userspecs_json(struct json_container *json, + struct sudoers_parse_tree *parse_tree, bool expand_aliases) { struct userspec *us; debug_decl(print_userspecs_json, SUDOERS_DEBUG_UTIL); if (TAILQ_EMPTY(&parse_tree->userspecs)) - debug_return_bool(need_comma); + debug_return; - fprintf(fp, "%s\n%*s\"User_Specs\": [\n", need_comma ? "," : "", indent, ""); - indent += 4; + sudo_json_open_array(json, "User_Specs"); TAILQ_FOREACH(us, &parse_tree->userspecs, entries) { - print_userspec_json(fp, parse_tree, us, indent, expand_aliases); + print_userspec_json(json, parse_tree, us, expand_aliases); } - indent -= 4; - fprintf(fp, "%*s]", indent, ""); + sudo_json_close_array(json); - debug_return_bool(true); + debug_return; } /* @@ -1120,8 +854,8 @@ bool convert_sudoers_json(struct sudoers_parse_tree *parse_tree, const char *output_file, struct cvtsudoers_config *conf) { - bool ret = true, need_comma = false; - const int indent = 4; + struct json_container json; + bool ret = true; FILE *output_fp = stdout; debug_decl(convert_sudoers_json, SUDOERS_DEBUG_UTIL); @@ -1131,24 +865,22 @@ convert_sudoers_json(struct sudoers_parse_tree *parse_tree, } /* Open JSON output. */ + sudo_json_init(&json, output_fp, 4); putc('{', output_fp); /* Dump Defaults in JSON format. */ if (!ISSET(conf->suppress, SUPPRESS_DEFAULTS)) { - need_comma = print_defaults_json(output_fp, parse_tree, indent, - conf->expand_aliases, need_comma); + print_defaults_json(&json, parse_tree, conf->expand_aliases); } /* Dump Aliases in JSON format. */ if (!conf->expand_aliases && !ISSET(conf->suppress, SUPPRESS_ALIASES)) { - need_comma = print_aliases_json(output_fp, parse_tree, indent, - need_comma); + print_aliases_json(&json, parse_tree); } /* Dump User_Specs in JSON format. */ if (!ISSET(conf->suppress, SUPPRESS_PRIVS)) { - print_userspecs_json(output_fp, parse_tree, indent, - conf->expand_aliases, need_comma); + print_userspecs_json(&json, parse_tree, conf->expand_aliases); } /* Close JSON output. */