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