1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include <config.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #ifdef HAVE_STDBOOL_H
29 # include <stdbool.h>
30 #else
31 # include "compat/stdbool.h"
32 #endif /* HAVE_STDBOOL_H */
33 #include <string.h>
34 #include <unistd.h>
35 #include <ctype.h>
36 #include <limits.h>
37 #include <fcntl.h>
38 #include <time.h>
39
40 #include "sudo_compat.h"
41 #include "sudo_debug.h"
42 #include "sudo_eventlog.h"
43 #include "sudo_fatal.h"
44 #include "sudo_gettext.h"
45 #include "sudo_iolog.h"
46 #include "sudo_util.h"
47
48 #include "iolog_json.h"
49
50 struct json_stack {
51 unsigned int depth;
52 unsigned int maxdepth;
53 struct json_object *frames[64];
54 };
55 #define JSON_STACK_INTIALIZER(s) { 0, nitems((s).frames) };
56
57 static bool
json_store_columns(struct json_item * item,struct eventlog * evlog)58 json_store_columns(struct json_item *item, struct eventlog *evlog)
59 {
60 debug_decl(json_store_columns, SUDO_DEBUG_UTIL);
61
62 if (item->u.number < 1 || item->u.number > INT_MAX) {
63 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
64 "tty cols %lld: out of range", item->u.number);
65 evlog->columns = 0;
66 debug_return_bool(false);
67 }
68
69 evlog->columns = item->u.number;
70 debug_return_bool(true);
71 }
72
73 static bool
json_store_command(struct json_item * item,struct eventlog * evlog)74 json_store_command(struct json_item *item, struct eventlog *evlog)
75 {
76 debug_decl(json_store_command, SUDO_DEBUG_UTIL);
77
78 /*
79 * Note: struct eventlog must store command + args.
80 * We don't have argv yet so we append the args later.
81 */
82 free(evlog->command);
83 evlog->command = item->u.string;
84 item->u.string = NULL;
85 debug_return_bool(true);
86 }
87
88 static bool
json_store_lines(struct json_item * item,struct eventlog * evlog)89 json_store_lines(struct json_item *item, struct eventlog *evlog)
90 {
91 debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
92
93 if (item->u.number < 1 || item->u.number > INT_MAX) {
94 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
95 "tty lines %lld: out of range", item->u.number);
96 evlog->lines = 0;
97 debug_return_bool(false);
98 }
99
100 evlog->lines = item->u.number;
101 debug_return_bool(true);
102 }
103
104 char **
json_array_to_strvec(struct json_object * array)105 json_array_to_strvec(struct json_object *array)
106 {
107 struct json_item *item;
108 int len = 0;
109 char **ret;
110 debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
111
112 TAILQ_FOREACH(item, &array->items, entries) {
113 /* Can only convert arrays of string. */
114 if (item->type != JSON_STRING) {
115 sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
116 debug_return_ptr(NULL);
117 }
118 /* Prevent integer overflow. */
119 if (++len == INT_MAX) {
120 sudo_warnx("%s", U_("JSON_ARRAY too large"));
121 debug_return_ptr(NULL);
122 }
123 }
124 if ((ret = reallocarray(NULL, len + 1, sizeof(char *))) == NULL) {
125 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
126 debug_return_ptr(NULL);
127 }
128 len = 0;
129 TAILQ_FOREACH(item, &array->items, entries) {
130 ret[len++] = item->u.string;
131 item->u.string = NULL;
132 }
133 ret[len] = NULL;
134
135 debug_return_ptr(ret);
136 }
137
138 static bool
json_store_runargv(struct json_item * item,struct eventlog * evlog)139 json_store_runargv(struct json_item *item, struct eventlog *evlog)
140 {
141 int i;
142 debug_decl(json_store_runargv, SUDO_DEBUG_UTIL);
143
144 if (evlog->argv != NULL) {
145 for (i = 0; evlog->argv[i] != NULL; i++)
146 free(evlog->argv[i]);
147 free(evlog->argv);
148 }
149 evlog->argv = json_array_to_strvec(&item->u.child);
150
151 debug_return_bool(evlog->argv != NULL);
152 }
153
154 static bool
json_store_runenv(struct json_item * item,struct eventlog * evlog)155 json_store_runenv(struct json_item *item, struct eventlog *evlog)
156 {
157 int i;
158 debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
159
160 if (evlog->envp != NULL) {
161 for (i = 0; evlog->envp[i] != NULL; i++)
162 free(evlog->envp[i]);
163 free(evlog->envp);
164 }
165 evlog->envp = json_array_to_strvec(&item->u.child);
166
167 debug_return_bool(evlog->envp != NULL);
168 }
169
170 static bool
json_store_rungid(struct json_item * item,struct eventlog * evlog)171 json_store_rungid(struct json_item *item, struct eventlog *evlog)
172 {
173 debug_decl(json_store_rungid, SUDO_DEBUG_UTIL);
174
175 evlog->rungid = (gid_t)item->u.number;
176 debug_return_bool(true);
177 }
178
179 static bool
json_store_rungroup(struct json_item * item,struct eventlog * evlog)180 json_store_rungroup(struct json_item *item, struct eventlog *evlog)
181 {
182 debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL);
183
184 free(evlog->rungroup);
185 evlog->rungroup = item->u.string;
186 item->u.string = NULL;
187 debug_return_bool(true);
188 }
189
190 static bool
json_store_runuid(struct json_item * item,struct eventlog * evlog)191 json_store_runuid(struct json_item *item, struct eventlog *evlog)
192 {
193 debug_decl(json_store_runuid, SUDO_DEBUG_UTIL);
194
195 evlog->runuid = (uid_t)item->u.number;
196 debug_return_bool(true);
197 }
198
199 static bool
json_store_runuser(struct json_item * item,struct eventlog * evlog)200 json_store_runuser(struct json_item *item, struct eventlog *evlog)
201 {
202 debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
203
204 free(evlog->runuser);
205 evlog->runuser = item->u.string;
206 item->u.string = NULL;
207 debug_return_bool(true);
208 }
209
210 static bool
json_store_runchroot(struct json_item * item,struct eventlog * evlog)211 json_store_runchroot(struct json_item *item, struct eventlog *evlog)
212 {
213 debug_decl(json_store_runchroot, SUDO_DEBUG_UTIL);
214
215 free(evlog->runchroot);
216 evlog->runchroot = item->u.string;
217 item->u.string = NULL;
218 debug_return_bool(true);
219 }
220
221 static bool
json_store_runcwd(struct json_item * item,struct eventlog * evlog)222 json_store_runcwd(struct json_item *item, struct eventlog *evlog)
223 {
224 debug_decl(json_store_runcwd, SUDO_DEBUG_UTIL);
225
226 free(evlog->runcwd);
227 evlog->runcwd = item->u.string;
228 item->u.string = NULL;
229 debug_return_bool(true);
230 }
231
232 static bool
json_store_submitcwd(struct json_item * item,struct eventlog * evlog)233 json_store_submitcwd(struct json_item *item, struct eventlog *evlog)
234 {
235 debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL);
236
237 free(evlog->cwd);
238 evlog->cwd = item->u.string;
239 item->u.string = NULL;
240 debug_return_bool(true);
241 }
242
243 static bool
json_store_submithost(struct json_item * item,struct eventlog * evlog)244 json_store_submithost(struct json_item *item, struct eventlog *evlog)
245 {
246 debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
247
248 free(evlog->submithost);
249 evlog->submithost = item->u.string;
250 item->u.string = NULL;
251 debug_return_bool(true);
252 }
253
254 static bool
json_store_submituser(struct json_item * item,struct eventlog * evlog)255 json_store_submituser(struct json_item *item, struct eventlog *evlog)
256 {
257 debug_decl(json_store_submituser, SUDO_DEBUG_UTIL);
258
259 free(evlog->submituser);
260 evlog->submituser = item->u.string;
261 item->u.string = NULL;
262 debug_return_bool(true);
263 }
264
265 static bool
json_store_timestamp(struct json_item * item,struct eventlog * evlog)266 json_store_timestamp(struct json_item *item, struct eventlog *evlog)
267 {
268 struct json_object *object;
269 debug_decl(json_store_timestamp, SUDO_DEBUG_UTIL);
270
271 object = &item->u.child;
272 TAILQ_FOREACH(item, &object->items, entries) {
273 if (item->type != JSON_NUMBER)
274 continue;
275 if (strcmp(item->name, "seconds") == 0) {
276 evlog->submit_time.tv_sec = item->u.number;
277 continue;
278 }
279 if (strcmp(item->name, "nanoseconds") == 0) {
280 evlog->submit_time.tv_nsec = item->u.number;
281 continue;
282 }
283 }
284 debug_return_bool(true);
285 }
286
287 static bool
json_store_ttyname(struct json_item * item,struct eventlog * evlog)288 json_store_ttyname(struct json_item *item, struct eventlog *evlog)
289 {
290 debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL);
291
292 free(evlog->ttyname);
293 evlog->ttyname = item->u.string;
294 item->u.string = NULL;
295 debug_return_bool(true);
296 }
297
298 static struct iolog_json_key {
299 const char *name;
300 enum json_value_type type;
301 bool (*setter)(struct json_item *, struct eventlog *);
302 } iolog_json_keys[] = {
303 { "columns", JSON_NUMBER, json_store_columns },
304 { "command", JSON_STRING, json_store_command },
305 { "lines", JSON_NUMBER, json_store_lines },
306 { "runargv", JSON_ARRAY, json_store_runargv },
307 { "runenv", JSON_ARRAY, json_store_runenv },
308 { "rungid", JSON_ID, json_store_rungid },
309 { "rungroup", JSON_STRING, json_store_rungroup },
310 { "runuid", JSON_ID, json_store_runuid },
311 { "runuser", JSON_STRING, json_store_runuser },
312 { "runchroot", JSON_STRING, json_store_runchroot },
313 { "runcwd", JSON_STRING, json_store_runcwd },
314 { "submitcwd", JSON_STRING, json_store_submitcwd },
315 { "submithost", JSON_STRING, json_store_submithost },
316 { "submituser", JSON_STRING, json_store_submituser },
317 { "timestamp", JSON_OBJECT, json_store_timestamp },
318 { "ttyname", JSON_STRING, json_store_ttyname },
319 { NULL }
320 };
321
322 static struct json_item *
new_json_item(enum json_value_type type,char * name,unsigned int lineno)323 new_json_item(enum json_value_type type, char *name, unsigned int lineno)
324 {
325 struct json_item *item;
326 debug_decl(new_json_item, SUDO_DEBUG_UTIL);
327
328 if ((item = malloc(sizeof(*item))) == NULL) {
329 sudo_warnx(U_("%s: %s"), __func__,
330 U_("unable to allocate memory"));
331 debug_return_ptr(NULL);
332 }
333 item->name = name;
334 item->type = type;
335 item->lineno = lineno;
336
337 debug_return_ptr(item);
338 }
339
340 static char *
json_parse_string(char ** strp)341 json_parse_string(char **strp)
342 {
343 char *dst, *end, *ret, *src = *strp + 1;
344 size_t len;
345 debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
346
347 for (end = src; *end != '"' && *end != '\0'; end++) {
348 if (end[0] == '\\' && end[1] == '"')
349 end++;
350 }
351 if (*end != '"') {
352 sudo_warnx("%s", U_("missing double quote in name"));
353 debug_return_str(NULL);
354 }
355 len = (size_t)(end - src);
356
357 /* Copy string, flattening escaped chars. */
358 dst = ret = malloc(len + 1);
359 if (dst == NULL) {
360 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
361 debug_return_str(NULL);
362 }
363 while (src < end) {
364 char ch = *src++;
365 /* TODO: handle unicode escapes */
366 if (ch == '\\') {
367 switch (*src) {
368 case 'b':
369 ch = '\b';
370 break;
371 case 'f':
372 ch = '\f';
373 break;
374 case 'n':
375 ch = '\n';
376 break;
377 case 'r':
378 ch = '\r';
379 break;
380 case 't':
381 ch = '\t';
382 break;
383 case '"':
384 case '\\':
385 default:
386 /* Note: a bare \ at the end of a string will be removed. */
387 ch = *src;
388 break;
389 }
390 src++;
391 }
392 *dst++ = ch;
393 }
394 *dst = '\0';
395
396 /* Trim trailing whitespace. */
397 do {
398 end++;
399 } while (isspace((unsigned char)*end));
400 *strp = end;
401
402 debug_return_str(ret);
403 }
404
405 void
free_json_items(struct json_item_list * items)406 free_json_items(struct json_item_list *items)
407 {
408 struct json_item *item;
409 debug_decl(free_json_items, SUDO_DEBUG_UTIL);
410
411 while ((item = TAILQ_FIRST(items)) != NULL) {
412 TAILQ_REMOVE(items, item, entries);
413 switch (item->type) {
414 case JSON_STRING:
415 free(item->u.string);
416 break;
417 case JSON_ARRAY:
418 case JSON_OBJECT:
419 free_json_items(&item->u.child.items);
420 break;
421 case JSON_ID:
422 case JSON_NUMBER:
423 case JSON_BOOL:
424 case JSON_NULL:
425 /* Nothing to free. */
426 break;
427 default:
428 sudo_warnx("%s: internal error, invalid JSON type %d",
429 __func__, item->type);
430 break;
431 }
432 free(item->name);
433 free(item);
434 }
435
436 debug_return;
437 }
438
439 static bool
iolog_parse_json_object(struct json_object * object,struct eventlog * evlog)440 iolog_parse_json_object(struct json_object *object, struct eventlog *evlog)
441 {
442 struct json_item *item;
443 bool ret = false;
444 debug_decl(iolog_parse_json_object, SUDO_DEBUG_UTIL);
445
446 /* First object holds all the actual data. */
447 item = TAILQ_FIRST(&object->items);
448 if (item == NULL) {
449 sudo_warnx("%s", U_("missing JSON_OBJECT"));
450 goto done;
451 }
452 if (item->type != JSON_OBJECT) {
453 sudo_warnx(U_("expected JSON_OBJECT, got %d"), item->type);
454 goto done;
455 }
456 object = &item->u.child;
457
458 TAILQ_FOREACH(item, &object->items, entries) {
459 struct iolog_json_key *key;
460
461 /* expecting key:value pairs */
462 if (item->name == NULL) {
463 sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
464 "%s: missing object name", __func__);
465 goto done;
466 }
467
468 /* lookup name */
469 for (key = iolog_json_keys; key->name != NULL; key++) {
470 if (strcmp(item->name, key->name) == 0)
471 break;
472 }
473 if (key->name == NULL) {
474 sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
475 "%s: unknown key %s", __func__, item->name);
476 } else if (key->type != item->type &&
477 (key->type != JSON_ID || item->type != JSON_NUMBER)) {
478 sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
479 "%s: key mismatch %s type %d, expected %d", __func__,
480 item->name, item->type, key->type);
481 goto done;
482 } else {
483 /* Matched name and type. */
484 if (!key->setter(item, evlog)) {
485 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
486 "unable to store %s", key->name);
487 goto done;
488 }
489 }
490 }
491
492 /* Merge cmd and argv as sudoreplay expects. */
493 if (evlog->command != NULL && evlog->argv != NULL && evlog->argv[0] != NULL) {
494 size_t len = strlen(evlog->command) + 1;
495 char *newcmd;
496 int ac;
497
498 /* Skip argv[0], we use evlog->command instead. */
499 for (ac = 1; evlog->argv[ac] != NULL; ac++)
500 len += strlen(evlog->argv[ac]) + 1;
501
502 if ((newcmd = malloc(len)) == NULL) {
503 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
504 goto done;
505 }
506
507 /* TODO: optimize this. */
508 if (strlcpy(newcmd, evlog->command, len) >= len)
509 sudo_fatalx(U_("internal error, %s overflow"), __func__);
510 for (ac = 1; evlog->argv[ac] != NULL; ac++) {
511 if (strlcat(newcmd, " ", len) >= len)
512 sudo_fatalx(U_("internal error, %s overflow"), __func__);
513 if (strlcat(newcmd, evlog->argv[ac], len) >= len)
514 sudo_fatalx(U_("internal error, %s overflow"), __func__);
515 }
516
517 free(evlog->command);
518 evlog->command = newcmd;
519 }
520
521 ret = true;
522
523 done:
524 debug_return_bool(ret);
525 }
526
527 static bool
json_insert_bool(struct json_item_list * items,char * name,bool value,unsigned int lineno)528 json_insert_bool(struct json_item_list *items, char *name, bool value,
529 unsigned int lineno)
530 {
531 struct json_item *item;
532 debug_decl(json_insert_bool, SUDO_DEBUG_UTIL);
533
534 if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL)
535 debug_return_bool(false);
536 item->u.boolean = value;
537 TAILQ_INSERT_TAIL(items, item, entries);
538
539 debug_return_bool(true);
540 }
541
542 static bool
json_insert_null(struct json_item_list * items,char * name,unsigned int lineno)543 json_insert_null(struct json_item_list *items, char *name, unsigned int lineno)
544 {
545 struct json_item *item;
546 debug_decl(json_insert_null, SUDO_DEBUG_UTIL);
547
548 if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL)
549 debug_return_bool(false);
550 TAILQ_INSERT_TAIL(items, item, entries);
551
552 debug_return_bool(true);
553 }
554
555 static bool
json_insert_num(struct json_item_list * items,char * name,long long value,unsigned int lineno)556 json_insert_num(struct json_item_list *items, char *name, long long value,
557 unsigned int lineno)
558 {
559 struct json_item *item;
560 debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
561
562 if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
563 debug_return_bool(false);
564 item->u.number = value;
565 TAILQ_INSERT_TAIL(items, item, entries);
566
567 debug_return_bool(true);
568 }
569
570 static bool
json_insert_str(struct json_item_list * items,char * name,char ** strp,unsigned int lineno)571 json_insert_str(struct json_item_list *items, char *name, char **strp,
572 unsigned int lineno)
573 {
574 struct json_item *item;
575 debug_decl(json_insert_str, SUDO_DEBUG_UTIL);
576
577 if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL)
578 debug_return_bool(false);
579 item->u.string = json_parse_string(strp);
580 if (item->u.string == NULL) {
581 free(item);
582 debug_return_bool(false);
583 }
584 TAILQ_INSERT_TAIL(items, item, entries);
585
586 debug_return_bool(true);
587 }
588
589 static struct json_object *
json_stack_push(struct json_stack * stack,struct json_item_list * items,struct json_object * frame,enum json_value_type type,char * name,unsigned int lineno)590 json_stack_push(struct json_stack *stack, struct json_item_list *items,
591 struct json_object *frame, enum json_value_type type, char *name,
592 unsigned int lineno)
593 {
594 struct json_item *item;
595 debug_decl(json_stack_push, SUDO_DEBUG_UTIL);
596
597 /* We limit the stack size rather than expanding it. */
598 if (stack->depth >= stack->maxdepth) {
599 sudo_warnx(U_("json stack exhausted (max %u frames)"), stack->maxdepth);
600 debug_return_ptr(NULL);
601 }
602
603 /* Allocate a new item and insert it into the list. */
604 if ((item = new_json_item(type, name, lineno)) == NULL)
605 debug_return_ptr(NULL);
606 TAILQ_INIT(&item->u.child.items);
607 item->u.child.parent = item;
608 TAILQ_INSERT_TAIL(items, item, entries);
609
610 /* Push the current frame onto the stack (depth check performed above). */
611 stack->frames[stack->depth++] = frame;
612
613 /* Return the new frame */
614 debug_return_ptr(&item->u.child);
615 }
616
617 /* Only expect a value if a name is defined or we are in an array. */
618 #define expect_value (name != NULL || (frame->parent != NULL && frame->parent->type == JSON_ARRAY))
619
620 bool
iolog_parse_json(FILE * fp,const char * filename,struct json_object * root)621 iolog_parse_json(FILE *fp, const char *filename, struct json_object *root)
622 {
623 struct json_object *frame = root;
624 struct json_stack stack = JSON_STACK_INTIALIZER(stack);
625 unsigned int lineno = 0;
626 char *name = NULL;
627 char *cp, *buf = NULL;
628 size_t bufsize = 0;
629 ssize_t len;
630 bool ret = false;
631 bool saw_comma = false;
632 long long num;
633 char ch;
634 debug_decl(iolog_parse_json, SUDO_DEBUG_UTIL);
635
636 root->parent = NULL;
637 TAILQ_INIT(&root->items);
638
639 while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) {
640 char *ep = buf + len - 1;
641 cp = buf;
642
643 lineno++;
644
645 /* Trim trailing whitespace. */
646 while (ep > cp && isspace((unsigned char)*ep))
647 ep--;
648 ep[1] = '\0';
649
650 for (;;) {
651 const char *errstr;
652
653 /* Trim leading whitespace, skip blank lines. */
654 while (isspace((unsigned char)*cp))
655 cp++;
656
657 /* Check for comma separator and strip it out. */
658 if (*cp == ',') {
659 saw_comma = true;
660 cp++;
661 while (isspace((unsigned char)*cp))
662 cp++;
663 }
664
665 /* End of line? */
666 if (*cp == '\0')
667 break;
668
669 switch (*cp) {
670 case '{':
671 if (name == NULL && frame->parent != NULL) {
672 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
673 U_("objects must consist of name:value pairs"));
674 goto done;
675 }
676 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
677 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
678 U_("missing separator between values"));
679 goto done;
680 }
681 cp++;
682 saw_comma = false;
683 frame = json_stack_push(&stack, &frame->items, frame,
684 JSON_OBJECT, name, lineno);
685 if (frame == NULL)
686 goto done;
687 name = NULL;
688 break;
689 case '}':
690 if (stack.depth == 0 || frame->parent == NULL ||
691 frame->parent->type != JSON_OBJECT) {
692 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
693 U_("unmatched close brace"));
694 goto done;
695 }
696 cp++;
697 frame = stack.frames[--stack.depth];
698 saw_comma = false;
699 break;
700 case '[':
701 if (frame->parent == NULL) {
702 /* Must have an enclosing object. */
703 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
704 U_("unexpected array"));
705 goto done;
706 }
707 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
708 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
709 U_("missing separator between values"));
710 goto done;
711 }
712 cp++;
713 saw_comma = false;
714 frame = json_stack_push(&stack, &frame->items, frame,
715 JSON_ARRAY, name, lineno);
716 if (frame == NULL)
717 goto done;
718 name = NULL;
719 break;
720 case ']':
721 if (stack.depth == 0 || frame->parent == NULL ||
722 frame->parent->type != JSON_ARRAY) {
723 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
724 U_("unmatched close bracket"));
725 goto done;
726 }
727 cp++;
728 frame = stack.frames[--stack.depth];
729 saw_comma = false;
730 break;
731 case '"':
732 if (frame->parent == NULL) {
733 /* Must have an enclosing object. */
734 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
735 U_("unexpected string"));
736 goto done;
737 }
738
739 if (!expect_value) {
740 /* Parse "name": */
741 if ((name = json_parse_string(&cp)) == NULL)
742 goto done;
743 /* TODO: allow colon on next line? */
744 if (*cp != ':') {
745 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
746 U_("missing colon after name"));
747 goto done;
748 }
749 cp++;
750 } else {
751 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
752 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
753 U_("missing separator between values"));
754 goto done;
755 }
756 saw_comma = false;
757 if (!json_insert_str(&frame->items, name, &cp, lineno))
758 goto done;
759 name = NULL;
760 }
761 break;
762 case 't':
763 if (strncmp(cp, "true", sizeof("true") - 1) != 0)
764 goto parse_error;
765 if (!expect_value) {
766 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
767 U_("unexpected boolean"));
768 goto done;
769 }
770 cp += sizeof("true") - 1;
771 if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
772 goto parse_error;
773 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
774 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
775 U_("missing separator between values"));
776 goto done;
777 }
778 saw_comma = false;
779
780 if (!json_insert_bool(&frame->items, name, true, lineno))
781 goto done;
782 name = NULL;
783 break;
784 case 'f':
785 if (strncmp(cp, "false", sizeof("false") - 1) != 0)
786 goto parse_error;
787 if (!expect_value) {
788 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
789 U_("unexpected boolean"));
790 goto done;
791 }
792 cp += sizeof("false") - 1;
793 if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
794 goto parse_error;
795 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
796 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
797 U_("missing separator between values"));
798 goto done;
799 }
800 saw_comma = false;
801
802 if (!json_insert_bool(&frame->items, name, false, lineno))
803 goto done;
804 name = NULL;
805 break;
806 case 'n':
807 if (strncmp(cp, "null", sizeof("null") - 1) != 0)
808 goto parse_error;
809 if (!expect_value) {
810 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
811 U_("unexpected null"));
812 goto done;
813 }
814 cp += sizeof("null") - 1;
815 if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
816 goto parse_error;
817 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
818 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
819 U_("missing separator between values"));
820 goto done;
821 }
822 saw_comma = false;
823
824 if (!json_insert_null(&frame->items, name, lineno))
825 goto done;
826 name = NULL;
827 break;
828 case '+': case '-': case '0': case '1': case '2': case '3':
829 case '4': case '5': case '6': case '7': case '8': case '9':
830 if (!expect_value) {
831 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
832 U_("unexpected number"));
833 goto done;
834 }
835 /* XXX - strtonumx() would be simpler here. */
836 len = strcspn(cp, " \f\n\r\t\v,");
837 ch = cp[len];
838 cp[len] = '\0';
839 if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
840 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
841 U_("missing separator between values"));
842 goto done;
843 }
844 saw_comma = false;
845 num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
846 if (errstr != NULL) {
847 sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - buf,
848 cp, U_(errstr));
849 goto done;
850 }
851 cp += len;
852 *cp = ch;
853
854 if (!json_insert_num(&frame->items, name, num, lineno))
855 goto done;
856 name = NULL;
857 break;
858 default:
859 goto parse_error;
860 }
861 }
862 }
863 if (stack.depth != 0) {
864 frame = stack.frames[stack.depth - 1];
865 if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) {
866 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
867 U_("unmatched close brace"));
868 } else {
869 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf,
870 U_("unmatched close bracket"));
871 }
872 goto done;
873 }
874
875 ret = true;
876 goto done;
877
878 parse_error:
879 sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("parse error"));
880 done:
881 free(buf);
882 free(name);
883 if (!ret)
884 free_json_items(&root->items);
885
886 debug_return_bool(ret);
887 }
888
889 bool
iolog_parse_loginfo_json(FILE * fp,const char * iolog_dir,struct eventlog * evlog)890 iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct eventlog *evlog)
891 {
892 struct json_object root;
893 bool ret = false;
894 debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
895
896 if (iolog_parse_json(fp, iolog_dir, &root)) {
897 /* Walk the stack and parse entries. */
898 ret = iolog_parse_json_object(&root, evlog);
899
900 /* Cleanup. */
901 free_json_items(&root.items);
902 }
903
904 debug_return_bool(ret);
905 }
906