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>