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