Return to visudo_json.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / sudo / plugins / sudoers |
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: }