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>