File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers / visudo_json.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 16:12:54 2014 UTC (10 years, 3 months ago) by misho
Branches: sudo, MAIN
CVS tags: v1_8_10p3_0, v1_8_10p3, HEAD
sudo v 1.8.10p3

    1: /*
    2:  * Copyright (c) 2013 Todd C. Miller <Todd.Miller@courtesan.com>
    3:  *
    4:  * Permission to use, copy, modify, and distribute this software for any
    5:  * purpose with or without fee is hereby granted, provided that the above
    6:  * copyright notice and this permission notice appear in all copies.
    7:  *
    8:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    9:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   10:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   11:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   12:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   13:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   14:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   15:  */
   16: 
   17: #include <config.h>
   18: 
   19: #include <sys/types.h>
   20: #include <stdio.h>
   21: #ifdef STDC_HEADERS
   22: # include <stdlib.h>
   23: # include <stddef.h>
   24: #else
   25: # ifdef HAVE_STDLIB_H
   26: #  include <stdlib.h>
   27: # endif
   28: #endif /* STDC_HEADERS */
   29: #ifdef HAVE_STRING_H
   30: # include <string.h>
   31: #endif /* HAVE_STRING_H */
   32: #ifdef HAVE_STRINGS_H
   33: # include <strings.h>
   34: #endif /* HAVE_STRINGS_H */
   35: #ifdef HAVE_UNISTD_H
   36: #include <unistd.h>
   37: #endif /* HAVE_UNISTD_H */
   38: #include <stdarg.h>
   39: #include <ctype.h>
   40: 
   41: #include "sudoers.h"
   42: #include "parse.h"
   43: #include <gram.h>
   44: 
   45: /*
   46:  * JSON values may be of the following types.
   47:  */
   48: enum json_value_type {
   49:     JSON_STRING,
   50:     JSON_ID,
   51:     JSON_NUMBER,
   52:     JSON_OBJECT,
   53:     JSON_ARRAY,
   54:     JSON_BOOL,
   55:     JSON_NULL
   56: };
   57: 
   58: /*
   59:  * JSON value suitable for printing.
   60:  * Note: this does not support object or array values.
   61:  */
   62: struct json_value {
   63:     enum json_value_type type;
   64:     union {
   65: 	char *string;
   66: 	int number;
   67: 	id_t id;
   68: 	bool boolean;
   69:     } u;
   70: };
   71: 
   72: /*
   73:  * Closure used to store state when iterating over all aliases.
   74:  */
   75: struct json_alias_closure {
   76:     FILE *fp;
   77:     const char *title;
   78:     unsigned int count;
   79:     int alias_type;
   80:     int indent;
   81:     bool need_comma;
   82: };
   83: 
   84: /*
   85:  * Type values used to disambiguate the generic WORD and ALIAS types.
   86:  */
   87: enum word_type {
   88:     TYPE_COMMAND,
   89:     TYPE_HOSTNAME,
   90:     TYPE_RUNASGROUP,
   91:     TYPE_RUNASUSER,
   92:     TYPE_USERNAME
   93: };
   94: 
   95: /*
   96:  * Print "indent" number of blank characters.
   97:  */
   98: static void
   99: print_indent(FILE *fp, int indent)
  100: {
  101:     while (indent--)
  102: 	putc(' ', fp);
  103: }
  104: 
  105: /*
  106:  * Print a JSON string, escaping special characters.
  107:  * Does not support unicode escapes.
  108:  */
  109: static void
  110: print_string_json_unquoted(FILE *fp, const char *str)
  111: {
  112:     char ch;
  113: 
  114:     while ((ch = *str++) != '\0') {
  115: 	switch (ch) {
  116: 	case '"':
  117: 	case '\\':
  118: 	case '/':
  119: 	    putc('\\', fp);
  120: 	    break;
  121: 	case '\b':
  122: 	    ch = 'b';
  123: 	    putc('\\', fp);
  124: 	    break;
  125: 	case '\f':
  126: 	    ch = 'f';
  127: 	    putc('\\', fp);
  128: 	    break;
  129: 	case '\n':
  130: 	    ch = 'n';
  131: 	    putc('\\', fp);
  132: 	    break;
  133: 	case '\r':
  134: 	    ch = 'r';
  135: 	    putc('\\', fp);
  136: 	    break;
  137: 	case '\t':
  138: 	    ch = 't';
  139: 	    putc('\\', fp);
  140: 	    break;
  141: 	}
  142: 	putc(ch, fp);
  143:     }
  144: }
  145: 
  146: /*
  147:  * Print a quoted JSON string, escaping special characters.
  148:  * Does not support unicode escapes.
  149:  */
  150: static void
  151: print_string_json(FILE *fp, const char *str)
  152: {
  153:     putc('\"', fp);
  154:     print_string_json_unquoted(fp, str);
  155:     putc('\"', fp);
  156: }
  157: 
  158: /*
  159:  * Print a JSON name: value pair with proper quoting and escaping.
  160:  */
  161: static void
  162: print_pair_json(FILE *fp, const char *pre, const char *name,
  163:     const struct json_value *value, const char *post, int indent)
  164: {
  165:     debug_decl(print_pair_json, SUDO_DEBUG_UTIL)
  166: 
  167:     print_indent(fp, indent);
  168: 
  169:     /* prefix */
  170:     if (pre != NULL)
  171: 	fputs(pre, fp);
  172: 
  173:     /* name */
  174:     print_string_json(fp, name);
  175:     putc(':', fp);
  176:     putc(' ', fp);
  177: 
  178:     /* value */
  179:     switch (value->type) {
  180:     case JSON_STRING:
  181: 	print_string_json(fp, value->u.string);
  182: 	break;
  183:     case JSON_ID:
  184: 	fprintf(fp, "%u", (unsigned int)value->u.id);
  185: 	break;
  186:     case JSON_NUMBER:
  187: 	fprintf(fp, "%d", value->u.number);
  188: 	break;
  189:     case JSON_NULL:
  190: 	fputs("null", fp);
  191: 	break;
  192:     case JSON_BOOL:
  193: 	fputs(value->u.boolean ? "true" : "false", fp);
  194: 	break;
  195:     case JSON_OBJECT:
  196: 	fatalx("internal error: can't print JSON_OBJECT");
  197: 	break;
  198:     case JSON_ARRAY:
  199: 	fatalx("internal error: can't print JSON_ARRAY");
  200: 	break;
  201:     }
  202: 
  203:     /* postfix */
  204:     if (post != NULL)
  205: 	fputs(post, fp);
  206: 
  207:     debug_return;
  208: }
  209: 
  210: /*
  211:  * Print a JSON string with optional prefix and postfix to fp.
  212:  * Strings are not quoted but are escaped as per the JSON spec.
  213:  */
  214: static void
  215: printstr_json(FILE *fp, const char *pre, const char *str, const char *post,
  216:     int indent)
  217: {
  218:     debug_decl(printstr_json, SUDO_DEBUG_UTIL)
  219: 
  220:     print_indent(fp, indent);
  221:     if (pre != NULL)
  222: 	fputs(pre, fp);
  223:     if (str != NULL) {
  224: 	print_string_json_unquoted(fp, str);
  225:     }
  226:     if (post != NULL)
  227: 	fputs(post, fp);
  228:     debug_return;
  229: }
  230: 
  231: /*
  232:  * Print sudo command member in JSON format, with specified indentation.
  233:  * If last_one is false, a comma will be printed before the newline
  234:  * that closes the object.
  235:  */
  236: static void
  237: print_command_json(FILE *fp, struct member *m, int indent, bool last_one)
  238: {
  239:     struct sudo_command *c = (struct sudo_command *)m->name;
  240:     struct json_value value;
  241:     const char *digest_name;
  242:     debug_decl(print_command_json, SUDO_DEBUG_UTIL)
  243: 
  244:     printstr_json(fp, "{", NULL, NULL, indent);
  245:     if (m->negated || c->digest != NULL) {
  246: 	putc('\n', fp);
  247: 	indent += 4;
  248:     } else {
  249: 	putc(' ', fp);
  250: 	indent = 0;
  251:     }
  252: 
  253:     /* Print command with optional command line args. */
  254:     if (c->args != NULL) {
  255: 	printstr_json(fp, "\"", "command", "\": ", indent);
  256: 	printstr_json(fp, "\"", c->cmnd, " ", 0);
  257: 	printstr_json(fp, NULL, c->args, "\"", 0);
  258:     } else {
  259: 	value.type = JSON_STRING;
  260: 	value.u.string = c->cmnd;
  261: 	print_pair_json(fp, NULL, "command", &value, NULL, indent);
  262:     }
  263: 
  264:     /* Optional digest. */
  265:     if (c->digest != NULL) {
  266: 	fputs(",\n", fp);
  267: 	switch (c->digest->digest_type) {
  268: 	case SUDO_DIGEST_SHA224:
  269: 	    digest_name = "sha224";
  270: 	    break;
  271: 	case SUDO_DIGEST_SHA256:
  272: 	    digest_name = "sha256";
  273: 	    break;
  274: 	case SUDO_DIGEST_SHA384:
  275: 	    digest_name = "sha384";
  276: 	    break;
  277: 	case SUDO_DIGEST_SHA512:
  278: 	    digest_name = "sha512";
  279: 	    break;
  280: 	default:
  281: 	    digest_name = "invalid digest";
  282: 	    break;
  283: 	}
  284: 	value.type = JSON_STRING;
  285: 	value.u.string = c->digest->digest_str;
  286: 	print_pair_json(fp, NULL, digest_name, &value, NULL, indent);
  287:     }
  288: 
  289:     /* Command may be negated. */
  290:     if (m->negated) {
  291: 	fputs(",\n", fp);
  292: 	value.type = JSON_BOOL;
  293: 	value.u.boolean = true;
  294: 	print_pair_json(fp, NULL, "negated", &value, NULL, indent);
  295:     }
  296: 
  297:     if (indent != 0) {
  298: 	indent -= 4;
  299: 	putc('\n', fp);
  300: 	print_indent(fp, indent);
  301:     } else {
  302: 	putc(' ', fp);
  303:     }
  304:     putc('}', fp);
  305:     if (!last_one)
  306: 	putc(',', fp);
  307:     putc('\n', fp);
  308: 
  309:     debug_return;
  310: }
  311: 
  312: /*
  313:  * Map an alias type to enum word_type.
  314:  */
  315: static enum word_type
  316: alias_to_word_type(int alias_type)
  317: {
  318:     switch (alias_type) {
  319:     case CMNDALIAS:
  320: 	return TYPE_COMMAND;
  321:     case HOSTALIAS:
  322: 	return TYPE_HOSTNAME;
  323:     case RUNASALIAS:
  324: 	return TYPE_RUNASUSER;
  325:     case USERALIAS:
  326: 	return TYPE_USERNAME;
  327:     default:
  328: 	fatalx_nodebug("unexpected alias type %d", alias_type);
  329:     }
  330: }
  331: 
  332: /*
  333:  * Map a Defaults type to enum word_type.
  334:  */
  335: static enum word_type
  336: defaults_to_word_type(int defaults_type)
  337: {
  338:     switch (defaults_type) {
  339:     case DEFAULTS_CMND:
  340: 	return TYPE_COMMAND;
  341:     case DEFAULTS_HOST:
  342: 	return TYPE_HOSTNAME;
  343:     case DEFAULTS_RUNAS:
  344: 	return TYPE_RUNASUSER;
  345:     case DEFAULTS_USER:
  346: 	return TYPE_USERNAME;
  347:     default:
  348: 	fatalx_nodebug("unexpected defaults type %d", defaults_type);
  349:     }
  350: }
  351: 
  352: /*
  353:  * Print struct member in JSON format, with specified indentation.
  354:  * If last_one is false, a comma will be printed before the newline
  355:  * that closes the object.
  356:  */
  357: static void
  358: print_member_json(FILE *fp, struct member *m, enum word_type word_type,
  359:     bool last_one, int indent)
  360: {
  361:     struct json_value value;
  362:     const char *typestr;
  363:     const char *errstr;
  364:     id_t id;
  365:     debug_decl(print_member_json, SUDO_DEBUG_UTIL)
  366: 
  367:     /* Most of the time we print a string. */
  368:     value.type = JSON_STRING;
  369:     value.u.string = m->name;
  370: 
  371:     switch (m->type) {
  372:     case USERGROUP:
  373: 	value.u.string++; /* skip leading '%' */
  374: 	if (*value.u.string == ':') {
  375: 	    value.u.string++;
  376: 	    typestr = "nonunixgroup";
  377: 	    if (*value.u.string == '#') {
  378: 		id = atoid(m->name + 3, NULL, NULL, &errstr);
  379: 		if (errstr != NULL) {
  380: 		    warningx("internal error: non-Unix group ID %s: \"%s\"",
  381: 			errstr, m->name);
  382: 		} else {
  383: 		    value.type = JSON_ID;
  384: 		    value.u.id = id;
  385: 		    typestr = "nonunixgid";
  386: 		}
  387: 	    }
  388: 	} else {
  389: 	    typestr = "usergroup";
  390: 	    if (*value.u.string == '#') {
  391: 		id = atoid(m->name + 2, NULL, NULL, &errstr);
  392: 		if (errstr != NULL) {
  393: 		    warningx("internal error: group ID %s: \"%s\"",
  394: 			errstr, m->name);
  395: 		} else {
  396: 		    value.type = JSON_ID;
  397: 		    value.u.id = id;
  398: 		    typestr = "usergid";
  399: 		}
  400: 	    }
  401: 	}
  402: 	break;
  403:     case NETGROUP:
  404: 	typestr = "netgroup";
  405: 	value.u.string++; /* skip leading '+' */
  406: 	break;
  407:     case NTWKADDR:
  408: 	typestr = "networkaddr";
  409: 	break;
  410:     case COMMAND:
  411: 	print_command_json(fp, m, indent, last_one);
  412: 	debug_return;
  413:     case WORD:
  414: 	switch (word_type) {
  415: 	case TYPE_HOSTNAME:
  416: 	    typestr = "hostname";
  417: 	    break;
  418: 	case TYPE_RUNASGROUP:
  419: 	    typestr = "usergroup";
  420: 	    break;
  421: 	case TYPE_RUNASUSER:
  422: 	case TYPE_USERNAME:
  423: 	    typestr = "username";
  424: 	    if (*value.u.string == '#') {
  425: 		id = atoid(m->name + 1, NULL, NULL, &errstr);
  426: 		if (errstr != NULL) {
  427: 		    warningx("internal error: user ID %s: \"%s\"",
  428: 			errstr, m->name);
  429: 		} else {
  430: 		    value.type = JSON_ID;
  431: 		    value.u.id = id;
  432: 		    typestr = "userid";
  433: 		}
  434: 	    }
  435: 	    break;
  436: 	default:
  437: 	    fatalx("unexpected word type %d", word_type);
  438: 	}
  439: 	break;
  440:     case ALL:
  441: 	value.u.string = "ALL";
  442: 	/* FALLTHROUGH */
  443:     case ALIAS:
  444: 	switch (word_type) {
  445: 	case TYPE_COMMAND:
  446: 	    typestr = "cmndalias";
  447: 	    break;
  448: 	case TYPE_HOSTNAME:
  449: 	    typestr = "hostalias";
  450: 	    break;
  451: 	case TYPE_RUNASGROUP:
  452: 	case TYPE_RUNASUSER:
  453: 	    typestr = "runasalias";
  454: 	    break;
  455: 	case TYPE_USERNAME:
  456: 	    typestr = "useralias";
  457: 	    break;
  458: 	default:
  459: 	    fatalx("unexpected word type %d", word_type);
  460: 	}
  461: 	break;
  462:     default:
  463: 	fatalx("unexpected member type %d", m->type);
  464:     }
  465: 
  466:     if (m->negated) {
  467: 	print_indent(fp, indent);
  468: 	fputs("{\n", fp);
  469: 	indent += 4;
  470: 	print_pair_json(fp, NULL, typestr, &value, ",\n", indent);
  471: 	value.type = JSON_BOOL;
  472: 	value.u.boolean = true;
  473: 	print_pair_json(fp, NULL, "negated", &value, "\n", indent);
  474: 	indent -= 4;
  475: 	print_indent(fp, indent);
  476: 	putc('}', fp);
  477:     } else {
  478: 	print_pair_json(fp, "{ ", typestr, &value, " }", indent);
  479:     }
  480:     if (!last_one)
  481: 	putc(',', fp);
  482:     putc('\n', fp);
  483: 
  484:     debug_return;
  485: }
  486: 
  487: /*
  488:  * Callback for alias_apply() to print an alias entry if it matches
  489:  * the type specified in the closure.
  490:  */
  491: int
  492: print_alias_json(void *v1, void *v2)
  493: {
  494:     struct alias *a = v1;
  495:     struct json_alias_closure *closure = v2;
  496:     struct member *m;
  497:     debug_decl(print_alias_json, SUDO_DEBUG_UTIL)
  498: 
  499:     if (a->type != closure->alias_type)
  500: 	debug_return_int(0);
  501: 
  502:     /* Open the aliases object or close the last entry, then open new one. */
  503:     if (closure->count++ == 0) {
  504: 	fprintf(closure->fp, "%s\n%*s\"%s\": {\n",
  505: 	    closure->need_comma ? "," : "", closure->indent, "",
  506: 	    closure->title);
  507: 	closure->indent += 4;
  508:     } else {
  509: 	fprintf(closure->fp, "%*s],\n", closure->indent, "");
  510:     }
  511:     printstr_json(closure->fp, "\"", a->name, "\": [\n", closure->indent);
  512: 
  513:     closure->indent += 4;
  514:     TAILQ_FOREACH(m, &a->members, entries) {
  515: 	print_member_json(closure->fp, m,
  516: 	    alias_to_word_type(closure->alias_type),
  517: 	    TAILQ_NEXT(m, entries) == NULL, closure->indent);
  518:     }
  519:     closure->indent -= 4;
  520:     debug_return_int(0);
  521: }
  522: 
  523: /*
  524:  * Print the binding for a Defaults entry of the specified type.
  525:  */
  526: static void
  527: print_binding_json(FILE *fp, struct member_list *binding, int type, int indent)
  528: {
  529:     struct member *m;
  530:     debug_decl(print_binding_json, SUDO_DEBUG_UTIL)
  531: 
  532:     if (TAILQ_EMPTY(binding))
  533: 	debug_return;
  534: 
  535:     fprintf(fp, "%*s\"Binding\": [\n", indent, "");
  536:     indent += 4;
  537: 
  538:     /* Print each member object in binding. */
  539:     TAILQ_FOREACH(m, binding, entries) {
  540: 	print_member_json(fp, m, defaults_to_word_type(type),
  541: 	     TAILQ_NEXT(m, entries) == NULL, indent);
  542:     }
  543: 
  544:     indent -= 4;
  545:     fprintf(fp, "%*s],\n", indent, "");
  546: 
  547:     debug_return;
  548: }
  549: 
  550: /*
  551:  * Print a Defaults list JSON format.
  552:  */
  553: static void
  554: print_defaults_list_json(FILE *fp, struct defaults *def, int indent)
  555: {
  556:     char savech, *start, *end = def->val;
  557:     struct json_value value;
  558:     debug_decl(print_defaults_list_json, SUDO_DEBUG_UTIL)
  559: 
  560:     fprintf(fp, "%*s{\n", indent, "");
  561:     indent += 4;
  562:     value.type = JSON_STRING;
  563:     switch (def->op) {
  564:     case '+':
  565: 	value.u.string = "list_add";
  566: 	break;
  567:     case '-':
  568: 	value.u.string = "list_remove";
  569: 	break;
  570:     case true:
  571: 	value.u.string = "list_assign";
  572: 	break;
  573:     default:
  574: 	warningx("internal error: unexpected list op %d", def->op);
  575: 	value.u.string = "unsupported";
  576: 	break;
  577:     }
  578:     print_pair_json(fp, NULL, "operation", &value, ",\n", indent);
  579:     printstr_json(fp, "\"", def->var, "\": [\n", indent);
  580:     indent += 4;
  581:     print_indent(fp, indent);
  582:     /* Split value into multiple space-separated words. */
  583:     do {
  584: 	/* Remove leading blanks, must have a non-empty string. */
  585: 	for (start = end; isblank((unsigned char)*start); start++)
  586: 	    ;
  587: 	if (*start == '\0')
  588: 	    break;
  589: 
  590: 	/* Find the end and print it. */
  591: 	for (end = start; *end && !isblank((unsigned char)*end); end++)
  592: 	    ;
  593: 	savech = *end;
  594: 	*end = '\0';
  595: 	print_string_json(fp, start);
  596: 	if (savech != '\0')
  597: 	    putc(',', fp);
  598: 	*end = savech;
  599:     } while (*end++ != '\0');
  600:     putc('\n', fp);
  601:     indent -= 4;
  602:     fprintf(fp, "%*s]\n", indent, "");
  603:     indent -= 4;
  604:     fprintf(fp, "%*s}", indent, "");
  605: 
  606:     debug_return;
  607: }
  608: 
  609: static int
  610: get_defaults_type(struct defaults *def)
  611: {
  612:     struct sudo_defs_types *cur;
  613: 
  614:     /* Look up def in table to find its type. */
  615:     for (cur = sudo_defs_table; cur->name; cur++) {
  616: 	if (strcmp(def->var, cur->name) == 0)
  617: 	    return cur->type;
  618:     }
  619:     return -1;
  620: }
  621: 
  622: /*
  623:  * Export all Defaults in JSON format.
  624:  */
  625: static bool
  626: print_defaults_json(FILE *fp, int indent, bool need_comma)
  627: {
  628:     struct json_value value;
  629:     struct defaults *def, *next;
  630:     int type;
  631:     debug_decl(print_defaults_json, SUDO_DEBUG_UTIL)
  632: 
  633:     if (TAILQ_EMPTY(&defaults))
  634: 	debug_return_bool(need_comma);
  635: 
  636:     fprintf(fp, "%s\n%*s\"Defaults\": [\n", need_comma ? "," : "", indent, "");
  637:     indent += 4;
  638: 
  639:     TAILQ_FOREACH_SAFE(def, &defaults, entries, next) {
  640: 	type = get_defaults_type(def);
  641: 	if (type == -1) {
  642: 	    warningx(U_("unknown defaults entry `%s'"), def->var);
  643: 	    /* XXX - just pass it through as a string anyway? */
  644: 	    continue;
  645: 	}
  646: 
  647: 	/* Found it, print object container and binding (if any). */
  648: 	fprintf(fp, "%*s{\n", indent, "");
  649: 	indent += 4;
  650: 	print_binding_json(fp, def->binding, def->type, indent);
  651: 
  652: 	/* Validation checks. */
  653: 	/* XXX - validate values in addition to names? */
  654: 
  655: 	/* Print options, merging ones with the same binding. */
  656: 	fprintf(fp, "%*s\"Options\": [\n", indent, "");
  657: 	indent += 4;
  658: 	for (;;) {
  659: 	    next = TAILQ_NEXT(def, entries);
  660: 	    /* XXX - need to update cur too */
  661: 	    if ((type & T_MASK) == T_FLAG || def->val == NULL) {
  662: 		value.type = JSON_BOOL;
  663: 		value.u.boolean = def->op;
  664: 		print_pair_json(fp, "{ ", def->var, &value, " }", indent);
  665: 	    } else if ((type & T_MASK) == T_LIST) {
  666: 		print_defaults_list_json(fp, def, indent);
  667: 	    } else {
  668: 		value.type = JSON_STRING;
  669: 		value.u.string = def->val;
  670: 		print_pair_json(fp, "{ ", def->var, &value, " }", indent);
  671: 	    }
  672: 	    if (next == NULL || def->binding != next->binding)
  673: 		break;
  674: 	    def = next;
  675: 	    type = get_defaults_type(def);
  676: 	    if (type == -1) {
  677: 		warningx(U_("unknown defaults entry `%s'"), def->var);
  678: 		/* XXX - just pass it through as a string anyway? */
  679: 		break;;
  680: 	    }
  681: 	    fputs(",\n", fp);
  682: 	}
  683: 	putc('\n', fp);
  684: 	indent -= 4;
  685: 	print_indent(fp, indent);
  686: 	fputs("]\n", fp);
  687: 	indent -= 4;
  688: 	print_indent(fp, indent);
  689: 	fprintf(fp, "}%s\n", next != NULL ? "," : "");
  690:     }
  691: 
  692:     /* Close Defaults array; comma (if any) & newline will be printer later. */
  693:     indent -= 4;
  694:     print_indent(fp, indent);
  695:     fputs("]", fp);
  696: 
  697:     debug_return_bool(true);
  698: }
  699: 
  700: /*
  701:  * Export all aliases of the specified type in JSON format.
  702:  * Iterates through the entire aliases tree.
  703:  */
  704: static bool
  705: print_aliases_by_type_json(FILE *fp, int alias_type, const char *title,
  706:     int indent, bool need_comma)
  707: {
  708:     struct json_alias_closure closure;
  709:     debug_decl(print_aliases_by_type_json, SUDO_DEBUG_UTIL)
  710: 
  711:     closure.fp = fp;
  712:     closure.indent = indent;
  713:     closure.count = 0;
  714:     closure.alias_type = alias_type;
  715:     closure.title = title;
  716:     closure.need_comma = need_comma;
  717:     alias_apply(print_alias_json, &closure);
  718:     if (closure.count != 0) {
  719: 	print_indent(fp, closure.indent);
  720: 	fputs("]\n", fp);
  721: 	closure.indent -= 4;
  722: 	print_indent(fp, closure.indent);
  723: 	putc('}', fp);
  724: 	need_comma = true;
  725:     }
  726: 
  727:     debug_return_bool(need_comma);
  728: }
  729: 
  730: /*
  731:  * Export all aliases in JSON format.
  732:  */
  733: static bool
  734: print_aliases_json(FILE *fp, int indent, bool need_comma)
  735: {
  736:     debug_decl(print_aliases_json, SUDO_DEBUG_UTIL)
  737: 
  738:     need_comma = print_aliases_by_type_json(fp, USERALIAS, "User_Aliases",
  739: 	indent, need_comma);
  740:     need_comma = print_aliases_by_type_json(fp, RUNASALIAS, "Runas_Aliases",
  741: 	indent, need_comma);
  742:     need_comma = print_aliases_by_type_json(fp, HOSTALIAS, "Host_Aliases",
  743: 	indent, need_comma);
  744:     need_comma = print_aliases_by_type_json(fp, CMNDALIAS, "Command_Aliases",
  745: 	indent, need_comma);
  746: 
  747:     debug_return_bool(need_comma);
  748: }
  749: 
  750: /* XXX these are all duplicated w/ parse.c */
  751: #define RUNAS_CHANGED(cs1, cs2) \
  752: 	(cs1 == NULL || cs2 == NULL || \
  753: 	 cs1->runasuserlist != cs2->runasuserlist || \
  754: 	 cs1->runasgrouplist != cs2->runasgrouplist)
  755: 
  756: #define TAG_SET(tt) \
  757: 	((tt) != UNSPEC && (tt) != IMPLIED)
  758: 
  759: #define TAGS_CHANGED(ot, nt) \
  760: 	((TAG_SET((nt).setenv) && (nt).setenv != (ot).setenv) || \
  761: 	 (TAG_SET((nt).noexec) && (nt).noexec != (ot).noexec) || \
  762: 	 (TAG_SET((nt).nopasswd) && (nt).nopasswd != (ot).nopasswd) || \
  763: 	 (TAG_SET((nt).log_input) && (nt).log_input != (ot).log_input) || \
  764: 	 (TAG_SET((nt).log_output) && (nt).log_output != (ot).log_output))
  765: 
  766: /*
  767:  * Print a Cmnd_Spec in JSON format at the specified indent level.
  768:  * A pointer to the next Cmnd_Spec is passed in to make it possible to
  769:  * merge adjacent entries that are identical in all but the command.
  770:  */
  771: static void
  772: print_cmndspec_json(FILE *fp, struct cmndspec *cs, struct cmndspec **nextp,
  773:     int indent)
  774: {
  775:     struct cmndspec *next = *nextp;
  776:     struct json_value value;
  777:     struct member *m;
  778:     bool last_one;
  779:     debug_decl(print_cmndspec_json, SUDO_DEBUG_UTIL)
  780: 
  781:     /* Open Cmnd_Spec object. */
  782:     fprintf(fp, "%*s{\n", indent, "");
  783:     indent += 4;
  784: 
  785:     /* Print runasuserlist */
  786:     if (cs->runasuserlist != NULL) {
  787: 	fprintf(fp, "%*s\"runasusers\": [\n", indent, "");
  788: 	indent += 4;
  789: 	TAILQ_FOREACH(m, cs->runasuserlist, entries) {
  790: 	    print_member_json(fp, m, TYPE_RUNASUSER,
  791: 		TAILQ_NEXT(m, entries) == NULL, indent);
  792: 	}
  793: 	indent -= 4;
  794: 	fprintf(fp, "%*s],\n", indent, "");
  795:     }
  796: 
  797:     /* Print runasgrouplist */
  798:     if (cs->runasgrouplist != NULL) {
  799: 	fprintf(fp, "%*s\"runasgroups\": [\n", indent, "");
  800: 	indent += 4;
  801: 	TAILQ_FOREACH(m, cs->runasgrouplist, entries) {
  802: 	    print_member_json(fp, m, TYPE_RUNASGROUP,
  803: 		TAILQ_NEXT(m, entries) == NULL, indent);
  804: 	}
  805: 	indent -= 4;
  806: 	fprintf(fp, "%*s],\n", indent, "");
  807:     }
  808: 
  809:     /* Print tags */
  810:     if (cs->tags.nopasswd != UNSPEC || cs->tags.noexec != UNSPEC ||
  811: 	cs->tags.setenv != UNSPEC || cs->tags.log_input != UNSPEC ||
  812: 	cs->tags.log_output != UNSPEC) {
  813: 	fprintf(fp, "%*s\"Options\": [\n", indent, "");
  814: 	indent += 4;
  815: 	if (cs->tags.nopasswd != UNSPEC) {
  816: 	    value.type = JSON_BOOL;
  817: 	    value.u.boolean = !cs->tags.nopasswd;
  818: 	    last_one = cs->tags.noexec == UNSPEC &&
  819: 		cs->tags.setenv == UNSPEC && cs->tags.log_input == UNSPEC &&
  820: 		cs->tags.log_output == UNSPEC;
  821: 	    print_pair_json(fp, "{ ", "authenticate", &value,
  822: 		last_one ? " }\n" : " },\n", indent);
  823: 	}
  824: 	if (cs->tags.noexec != UNSPEC) {
  825: 	    value.type = JSON_BOOL;
  826: 	    value.u.boolean = cs->tags.noexec;
  827: 	    last_one = cs->tags.setenv == UNSPEC &&
  828: 		cs->tags.log_input == UNSPEC && cs->tags.log_output == UNSPEC;
  829: 	    print_pair_json(fp, "{ ", "noexec", &value,
  830: 		last_one ? " }\n" : " },\n", indent);
  831: 	}
  832: 	if (cs->tags.setenv != UNSPEC) {
  833: 	    value.type = JSON_BOOL;
  834: 	    value.u.boolean = cs->tags.setenv;
  835: 	    last_one = cs->tags.log_input == UNSPEC &&
  836: 		cs->tags.log_output == UNSPEC;
  837: 	    print_pair_json(fp, "{ ", "setenv", &value,
  838: 		last_one ? " }\n" : " },\n", indent);
  839: 	}
  840: 	if (cs->tags.log_input != UNSPEC) {
  841: 	    value.type = JSON_BOOL;
  842: 	    value.u.boolean = cs->tags.log_input;
  843: 	    last_one = cs->tags.log_output == UNSPEC;
  844: 	    print_pair_json(fp, "{ ", "log_input", &value,
  845: 		last_one ? " }\n" : " },\n", indent);
  846: 	}
  847: 	if (cs->tags.log_output != UNSPEC) {
  848: 	    value.type = JSON_BOOL;
  849: 	    value.u.boolean = cs->tags.log_output;
  850: 	    print_pair_json(fp, "{ ", "log_output", &value, " }\n", indent);
  851: 	}
  852: 	indent -= 4;
  853: 	fprintf(fp, "%*s],\n", indent, "");
  854:     }
  855: 
  856: #ifdef HAVE_SELINUX
  857:     /* Print SELinux role/type */
  858:     if (cs->role != NULL && cs->type != NULL) {
  859: 	fprintf(fp, "%*s\"SELinux_Spec\": [\n", indent, "");
  860: 	indent += 4;
  861: 	value.type = JSON_STRING;
  862: 	value.u.string = cs->role;
  863: 	print_pair_json(fp, NULL, "role", &value, ",\n", indent);
  864: 	value.u.string = cs->type;
  865: 	print_pair_json(fp, NULL, "type", &value, "\n", indent);
  866: 	indent -= 4;
  867: 	fprintf(fp, "%*s],\n", indent, "");
  868:     }
  869: #endif /* HAVE_SELINUX */
  870: 
  871: #ifdef HAVE_PRIV_SET
  872:     /* Print Solaris privs/limitprivs */
  873:     if (cs->privs != NULL || cs->limitprivs != NULL) {
  874: 	fprintf(fp, "%*s\"Solaris_Priv_Spec\": [\n", indent, "");
  875: 	indent += 4;
  876: 	value.type = JSON_STRING;
  877: 	if (cs->privs != NULL) {
  878: 	    value.u.string = cs->privs;
  879: 	    print_pair_json(fp, NULL, "privs", &value,
  880: 		cs->limitprivs != NULL ? ",\n" : "\n", indent);
  881: 	}
  882: 	if (cs->limitprivs != NULL) {
  883: 	    value.u.string = cs->limitprivs;
  884: 	    print_pair_json(fp, NULL, "limitprivs", &value, "\n", indent);
  885: 	}
  886: 	indent -= 4;
  887: 	fprintf(fp, "%*s],\n", indent, "");
  888:     }
  889: #endif /* HAVE_PRIV_SET */
  890: 
  891:     /*
  892:      * Merge adjacent commands with matching tags, runas, SELinux
  893:      * role/type and Solaris priv settings.
  894:      */
  895:     fprintf(fp, "%*s\"Commands\": [\n", indent, "");
  896:     indent += 4;
  897:     for (;;) {
  898: 	/* Does the next entry differ only in the command itself? */
  899: 	/* XXX - move into a function that returns bool */
  900: 	last_one = next == NULL ||
  901: 	    RUNAS_CHANGED(cs, next) || TAGS_CHANGED(cs->tags, next->tags)
  902: #ifdef HAVE_PRIV_SET
  903: 	    || cs->privs != next->privs || cs->limitprivs != next->limitprivs
  904: #endif /* HAVE_PRIV_SET */
  905: #ifdef HAVE_SELINUX
  906: 	    || cs->role != next->role || cs->type != next->type
  907: #endif /* HAVE_SELINUX */
  908: 	    ;
  909: 
  910: 	print_member_json(fp, cs->cmnd, TYPE_COMMAND, last_one, indent);
  911: 	if (last_one)
  912: 	    break;
  913: 	cs = next;
  914: 	next = TAILQ_NEXT(cs, entries);
  915:     }
  916:     indent -= 4;
  917:     fprintf(fp, "%*s]\n", indent, "");
  918: 
  919:     /* Close Cmnd_Spec object. */
  920:     indent -= 4;
  921:     fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(cs, entries) != NULL ? "," : "");
  922: 
  923:     *nextp = next;
  924: 
  925:     debug_return;
  926: }
  927: 
  928: /*
  929:  * Print a User_Spec in JSON format at the specified indent level.
  930:  */
  931: static void
  932: print_userspec_json(FILE *fp, struct userspec *us, int indent)
  933: {
  934:     struct privilege *priv;
  935:     struct member *m;
  936:     struct cmndspec *cs, *next;
  937:     debug_decl(print_userspec_json, SUDO_DEBUG_UTIL)
  938: 
  939:     /*
  940:      * Each userspec struct may contain multiple privileges for
  941:      * a user.  We export each privilege as a separate User_Spec
  942:      * object for simplicity's sake.
  943:      */
  944:     TAILQ_FOREACH(priv, &us->privileges, entries) {
  945: 	/* Open User_Spec object. */
  946: 	fprintf(fp, "%*s{\n", indent, "");
  947: 	indent += 4;
  948: 
  949: 	/* Print users list. */
  950: 	fprintf(fp, "%*s\"User_List\": [\n", indent, "");
  951: 	indent += 4;
  952: 	TAILQ_FOREACH(m, &us->users, entries) {
  953: 	    print_member_json(fp, m, TYPE_USERNAME,
  954: 		TAILQ_NEXT(m, entries) == NULL, indent);
  955: 	}
  956: 	indent -= 4;
  957: 	fprintf(fp, "%*s],\n", indent, "");
  958: 
  959: 	/* Print hosts list. */
  960: 	fprintf(fp, "%*s\"Host_List\": [\n", indent, "");
  961: 	indent += 4;
  962: 	TAILQ_FOREACH(m, &priv->hostlist, entries) {
  963: 	    print_member_json(fp, m, TYPE_HOSTNAME,
  964: 		TAILQ_NEXT(m, entries) == NULL, indent);
  965: 	}
  966: 	indent -= 4;
  967: 	fprintf(fp, "%*s],\n", indent, "");
  968: 
  969: 	/* Print commands. */
  970: 	fprintf(fp, "%*s\"Cmnd_Specs\": [\n", indent, "");
  971: 	indent += 4;
  972: 	TAILQ_FOREACH_SAFE(cs, &priv->cmndlist, entries, next) {
  973: 	    print_cmndspec_json(fp, cs, &next, indent);
  974: 	}
  975: 	indent -= 4;
  976: 	fprintf(fp, "%*s]\n", indent, "");
  977: 
  978: 	/* Close User_Spec object. */
  979: 	indent -= 4;
  980: 	fprintf(fp, "%*s}%s\n", indent, "", TAILQ_NEXT(priv, entries) != NULL ||
  981: 	    TAILQ_NEXT(us, entries) != NULL ? "," : "");
  982:     }
  983: 
  984:     debug_return;
  985: }
  986: 
  987: static bool
  988: print_userspecs_json(FILE *fp, int indent, bool need_comma)
  989: {
  990:     struct userspec *us;
  991:     debug_decl(print_userspecs_json, SUDO_DEBUG_UTIL)
  992: 
  993:     if (TAILQ_EMPTY(&userspecs))
  994: 	debug_return_bool(need_comma);
  995: 
  996:     fprintf(fp, "%s\n%*s\"User_Specs\": [\n", need_comma ? "," : "", indent, "");
  997:     indent += 4;
  998:     TAILQ_FOREACH(us, &userspecs, entries) {
  999: 	print_userspec_json(fp, us, indent);
 1000:     }
 1001:     indent -= 4;
 1002:     fprintf(fp, "%*s]", indent, "");
 1003: 
 1004:     debug_return_bool(true);
 1005: }
 1006: 
 1007: /*
 1008:  * Export the parsed sudoers file in JSON format.
 1009:  * XXX - ignores strict flag and doesn't pass through quiet flag
 1010:  */
 1011: bool
 1012: export_sudoers(const char *sudoers_path, const char *export_path,
 1013:     bool quiet, bool strict)
 1014: {
 1015:     bool ok = false, need_comma = false;
 1016:     const int indent = 4;
 1017:     FILE *export_fp = stdout;
 1018:     debug_decl(export_sudoers, SUDO_DEBUG_UTIL)
 1019: 
 1020:     if (strcmp(sudoers_path, "-") == 0) {
 1021: 	sudoersin = stdin;
 1022: 	sudoers_path = "stdin";
 1023:     } else if ((sudoersin = fopen(sudoers_path, "r")) == NULL) {
 1024: 	if (!quiet)
 1025: 	    warning(U_("unable to open %s"), sudoers_path);
 1026: 	goto done;
 1027:     }
 1028:     if (strcmp(export_path, "-") != 0) {
 1029: 	if ((export_fp = fopen(export_path, "w")) == NULL) {
 1030: 	    if (!quiet)
 1031: 		warning(U_("unable to open %s"), export_path);
 1032: 	    goto done;
 1033: 	}
 1034:     }
 1035:     init_parser(sudoers_path, quiet);
 1036:     if (sudoersparse() && !parse_error) {
 1037: 	if (!quiet)
 1038: 	    warningx(U_("failed to parse %s file, unknown error"), sudoers_path);
 1039: 	parse_error = true;
 1040: 	errorfile = sudoers_path;
 1041:     }
 1042:     ok = !parse_error;
 1043: 
 1044:     if (parse_error) {
 1045: 	if (!quiet) {
 1046: 	    if (errorlineno != -1)
 1047: 		warningx(_("parse error in %s near line %d\n"),
 1048: 		    errorfile, errorlineno);
 1049: 	    else if (errorfile != NULL)
 1050: 		warningx(_("parse error in %s\n"), errorfile);
 1051: 	}
 1052: 	goto done;
 1053:     }
 1054: 
 1055:     /* Open JSON output. */
 1056:     putc('{', export_fp);
 1057: 
 1058:     /* Dump Defaults in JSON format. */
 1059:     need_comma = print_defaults_json(export_fp, indent, need_comma);
 1060: 
 1061:     /* Dump Aliases in JSON format. */
 1062:     need_comma = print_aliases_json(export_fp, indent, need_comma);
 1063: 
 1064:     /* Dump User_Specs in JSON format. */
 1065:     print_userspecs_json(export_fp, indent, need_comma);
 1066: 
 1067:     /* Close JSON output. */
 1068:     fputs("\n}\n", export_fp);
 1069: 
 1070: done:
 1071:     if (export_fp != stdout)
 1072: 	fclose(export_fp);
 1073:     debug_return_bool(ok);
 1074: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>