Annotation of embedaddon/sudo/plugins/sudoers/visudo_json.c, revision 1.1
1.1 ! misho 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>