Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/lib/eventlog/parse_json.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2020-2023 Todd C. Miller <[email protected]>
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
#include <config.h>
20
21
#include <stdio.h>
22
#include <stdlib.h>
23
#ifdef HAVE_STDBOOL_H
24
# include <stdbool.h>
25
#else
26
# include <compat/stdbool.h>
27
#endif /* HAVE_STDBOOL_H */
28
#include <string.h>
29
#include <unistd.h>
30
#include <ctype.h>
31
#include <limits.h>
32
#include <fcntl.h>
33
#include <time.h>
34
35
#include <sudo_compat.h>
36
#include <sudo_debug.h>
37
#include <sudo_eventlog.h>
38
#include <sudo_fatal.h>
39
#include <sudo_gettext.h>
40
#include <sudo_util.h>
41
42
#include <parse_json.h>
43
44
struct json_stack {
45
unsigned int depth;
46
unsigned int maxdepth;
47
struct eventlog_json_object *frames[64];
48
};
49
#define JSON_STACK_INTIALIZER(s) { 0, nitems((s).frames) };
50
51
static char *iolog_file;
52
53
static bool
54
json_store_columns(struct json_item *item, struct eventlog *evlog)
55
{
56
debug_decl(json_store_columns, SUDO_DEBUG_UTIL);
57
58
if (item->u.number < 1 || item->u.number > INT_MAX) {
59
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
60
"tty cols %lld: out of range", item->u.number);
61
evlog->columns = 0;
62
debug_return_bool(false);
63
}
64
65
evlog->columns = (int)item->u.number;
66
debug_return_bool(true);
67
}
68
69
static bool
70
json_store_command(struct json_item *item, struct eventlog *evlog)
71
{
72
debug_decl(json_store_command, SUDO_DEBUG_UTIL);
73
74
/*
75
* Note: struct eventlog must store command + args.
76
* We don't have argv yet so we append the args later.
77
*/
78
free(evlog->command);
79
evlog->command = item->u.string;
80
item->u.string = NULL;
81
debug_return_bool(true);
82
}
83
84
static bool
85
json_store_dumped_core(struct json_item *item, struct eventlog *evlog)
86
{
87
debug_decl(json_store_dumped_core, SUDO_DEBUG_UTIL);
88
89
evlog->dumped_core = item->u.boolean;
90
debug_return_bool(true);
91
}
92
93
static bool
94
json_store_exit_value(struct json_item *item, struct eventlog *evlog)
95
{
96
debug_decl(json_store_exit_value, SUDO_DEBUG_UTIL);
97
98
if (item->u.number < 0 || item->u.number > INT_MAX) {
99
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
100
"exit value %lld: out of range", item->u.number);
101
evlog->exit_value = -1;
102
debug_return_bool(false);
103
}
104
105
evlog->exit_value = (int)item->u.number;
106
debug_return_bool(true);
107
}
108
109
static bool
110
json_store_iolog_file(struct json_item *item, struct eventlog *evlog)
111
{
112
debug_decl(json_store_iolog_file, SUDO_DEBUG_UTIL);
113
114
/* Do set evlog->iolog_file directly, it is a substring of iolog_path. */
115
free(iolog_file);
116
iolog_file = item->u.string;
117
item->u.string = NULL;
118
debug_return_bool(true);
119
}
120
121
static bool
122
json_store_iolog_path(struct json_item *item, struct eventlog *evlog)
123
{
124
debug_decl(json_store_iolog_path, SUDO_DEBUG_UTIL);
125
126
free(evlog->iolog_path);
127
evlog->iolog_path = item->u.string;
128
item->u.string = NULL;
129
debug_return_bool(true);
130
}
131
132
static bool
133
json_store_lines(struct json_item *item, struct eventlog *evlog)
134
{
135
debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
136
137
if (item->u.number < 1 || item->u.number > INT_MAX) {
138
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
139
"tty lines %lld: out of range", item->u.number);
140
evlog->lines = 0;
141
debug_return_bool(false);
142
}
143
144
evlog->lines = (int)item->u.number;
145
debug_return_bool(true);
146
}
147
148
static bool
149
json_store_peeraddr(struct json_item *item, struct eventlog *evlog)
150
{
151
debug_decl(json_store_peeraddr, SUDO_DEBUG_UTIL);
152
153
free(evlog->peeraddr);
154
evlog->peeraddr = item->u.string;
155
item->u.string = NULL;
156
debug_return_bool(true);
157
}
158
159
static char **
160
json_array_to_strvec(struct eventlog_json_object *array)
161
{
162
struct json_item *item;
163
size_t len = 0;
164
char **ret;
165
debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
166
167
TAILQ_FOREACH(item, &array->items, entries) {
168
/* Can only convert arrays of string. */
169
if (item->type != JSON_STRING) {
170
sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
171
debug_return_ptr(NULL);
172
}
173
/* Prevent integer overflow. */
174
if (++len == INT_MAX) {
175
sudo_warnx("%s", U_("JSON_ARRAY too large"));
176
debug_return_ptr(NULL);
177
}
178
}
179
if ((ret = reallocarray(NULL, len + 1, sizeof(char *))) == NULL) {
180
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
181
debug_return_ptr(NULL);
182
}
183
len = 0;
184
TAILQ_FOREACH(item, &array->items, entries) {
185
ret[len++] = item->u.string;
186
item->u.string = NULL;
187
}
188
ret[len] = NULL;
189
190
debug_return_ptr(ret);
191
}
192
193
static bool
194
json_store_submitenv(struct json_item *item, struct eventlog *evlog)
195
{
196
size_t i;
197
debug_decl(json_store_submitenv, SUDO_DEBUG_UTIL);
198
199
if (evlog->submitenv != NULL) {
200
for (i = 0; evlog->submitenv[i] != NULL; i++)
201
free(evlog->submitenv[i]);
202
free(evlog->submitenv);
203
}
204
evlog->submitenv = json_array_to_strvec(&item->u.child);
205
206
debug_return_bool(evlog->submitenv != NULL);
207
}
208
209
static bool
210
json_store_runargv(struct json_item *item, struct eventlog *evlog)
211
{
212
size_t i;
213
debug_decl(json_store_runargv, SUDO_DEBUG_UTIL);
214
215
if (evlog->runargv != NULL) {
216
for (i = 0; evlog->runargv[i] != NULL; i++)
217
free(evlog->runargv[i]);
218
free(evlog->runargv);
219
}
220
evlog->runargv = json_array_to_strvec(&item->u.child);
221
222
debug_return_bool(evlog->runargv != NULL);
223
}
224
225
static bool
226
json_store_runenv(struct json_item *item, struct eventlog *evlog)
227
{
228
size_t i;
229
debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
230
231
if (evlog->runenv != NULL) {
232
for (i = 0; evlog->runenv[i] != NULL; i++)
233
free(evlog->runenv[i]);
234
free(evlog->runenv);
235
}
236
evlog->runenv = json_array_to_strvec(&item->u.child);
237
238
debug_return_bool(evlog->runenv != NULL);
239
}
240
241
static bool
242
json_store_runenv_override(struct json_item *item, struct eventlog *evlog)
243
{
244
size_t i;
245
debug_decl(json_store_runenv_override, SUDO_DEBUG_UTIL);
246
247
if (evlog->env_add != NULL) {
248
for (i = 0; evlog->env_add[i] != NULL; i++)
249
free(evlog->env_add[i]);
250
free(evlog->env_add);
251
}
252
evlog->env_add = json_array_to_strvec(&item->u.child);
253
254
debug_return_bool(evlog->env_add != NULL);
255
}
256
257
static bool
258
json_store_rungid(struct json_item *item, struct eventlog *evlog)
259
{
260
debug_decl(json_store_rungid, SUDO_DEBUG_UTIL);
261
262
evlog->rungid = (gid_t)item->u.number;
263
debug_return_bool(true);
264
}
265
266
static bool
267
json_store_rungroup(struct json_item *item, struct eventlog *evlog)
268
{
269
debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL);
270
271
free(evlog->rungroup);
272
evlog->rungroup = item->u.string;
273
item->u.string = NULL;
274
debug_return_bool(true);
275
}
276
277
static bool
278
json_store_runuid(struct json_item *item, struct eventlog *evlog)
279
{
280
debug_decl(json_store_runuid, SUDO_DEBUG_UTIL);
281
282
evlog->runuid = (uid_t)item->u.number;
283
debug_return_bool(true);
284
}
285
286
static bool
287
json_store_runuser(struct json_item *item, struct eventlog *evlog)
288
{
289
debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
290
291
free(evlog->runuser);
292
evlog->runuser = item->u.string;
293
item->u.string = NULL;
294
debug_return_bool(true);
295
}
296
297
static bool
298
json_store_runchroot(struct json_item *item, struct eventlog *evlog)
299
{
300
debug_decl(json_store_runchroot, SUDO_DEBUG_UTIL);
301
302
free(evlog->runchroot);
303
evlog->runchroot = item->u.string;
304
item->u.string = NULL;
305
debug_return_bool(true);
306
}
307
308
static bool
309
json_store_runcwd(struct json_item *item, struct eventlog *evlog)
310
{
311
debug_decl(json_store_runcwd, SUDO_DEBUG_UTIL);
312
313
free(evlog->runcwd);
314
evlog->runcwd = item->u.string;
315
item->u.string = NULL;
316
debug_return_bool(true);
317
}
318
319
static bool
320
json_store_signal(struct json_item *item, struct eventlog *evlog)
321
{
322
debug_decl(json_store_signal, SUDO_DEBUG_UTIL);
323
324
free(evlog->signal_name);
325
evlog->signal_name = item->u.string;
326
item->u.string = NULL;
327
debug_return_bool(true);
328
}
329
330
static bool
331
json_store_source(struct json_item *item, struct eventlog *evlog)
332
{
333
debug_decl(json_store_source, SUDO_DEBUG_UTIL);
334
335
free(evlog->source);
336
evlog->source = item->u.string;
337
item->u.string = NULL;
338
debug_return_bool(true);
339
}
340
341
static bool
342
json_store_submitcwd(struct json_item *item, struct eventlog *evlog)
343
{
344
debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL);
345
346
free(evlog->cwd);
347
evlog->cwd = item->u.string;
348
item->u.string = NULL;
349
debug_return_bool(true);
350
}
351
352
static bool
353
json_store_submithost(struct json_item *item, struct eventlog *evlog)
354
{
355
debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
356
357
free(evlog->submithost);
358
evlog->submithost = item->u.string;
359
item->u.string = NULL;
360
debug_return_bool(true);
361
}
362
363
static bool
364
json_store_submituser(struct json_item *item, struct eventlog *evlog)
365
{
366
debug_decl(json_store_submituser, SUDO_DEBUG_UTIL);
367
368
free(evlog->submituser);
369
evlog->submituser = item->u.string;
370
item->u.string = NULL;
371
debug_return_bool(true);
372
}
373
374
static bool
375
json_store_submitgroup(struct json_item *item, struct eventlog *evlog)
376
{
377
debug_decl(json_store_submitgroup, SUDO_DEBUG_UTIL);
378
379
free(evlog->submitgroup);
380
evlog->submitgroup = item->u.string;
381
item->u.string = NULL;
382
debug_return_bool(true);
383
}
384
385
static bool
386
json_store_timespec(struct json_item *item, struct timespec *ts)
387
{
388
struct eventlog_json_object *object;
389
debug_decl(json_store_timespec, SUDO_DEBUG_UTIL);
390
391
object = &item->u.child;
392
TAILQ_FOREACH(item, &object->items, entries) {
393
if (item->type != JSON_NUMBER)
394
continue;
395
if (strcmp(item->name, "seconds") == 0) {
396
ts->tv_sec = (time_t)item->u.number;
397
continue;
398
}
399
if (strcmp(item->name, "nanoseconds") == 0) {
400
ts->tv_nsec = (long)item->u.number;
401
continue;
402
}
403
}
404
debug_return_bool(true);
405
}
406
407
static bool
408
json_store_iolog_offset(struct json_item *item, struct eventlog *evlog)
409
{
410
return json_store_timespec(item, &evlog->iolog_offset);
411
}
412
413
static bool
414
json_store_run_time(struct json_item *item, struct eventlog *evlog)
415
{
416
return json_store_timespec(item, &evlog->run_time);
417
}
418
419
static bool
420
json_store_timestamp(struct json_item *item, struct eventlog *evlog)
421
{
422
return json_store_timespec(item, &evlog->event_time);
423
}
424
425
static bool
426
json_store_ttyname(struct json_item *item, struct eventlog *evlog)
427
{
428
debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL);
429
430
free(evlog->ttyname);
431
evlog->ttyname = item->u.string;
432
item->u.string = NULL;
433
debug_return_bool(true);
434
}
435
436
static bool
437
json_store_uuid(struct json_item *item, struct eventlog *evlog)
438
{
439
bool ret = false;
440
debug_decl(json_store_uuid, SUDO_DEBUG_UTIL);
441
442
if (strlen(item->u.string) == sizeof(evlog->uuid_str) - 1) {
443
memcpy(evlog->uuid_str, item->u.string, sizeof(evlog->uuid_str));
444
ret = true;
445
}
446
free(item->u.string);
447
item->u.string = NULL;
448
debug_return_bool(ret);
449
}
450
451
static struct evlog_json_key {
452
const char *name;
453
enum json_value_type type;
454
bool (*setter)(struct json_item *, struct eventlog *);
455
} evlog_json_keys[] = {
456
{ "columns", JSON_NUMBER, json_store_columns },
457
{ "command", JSON_STRING, json_store_command },
458
{ "dumped_core", JSON_BOOL, json_store_dumped_core },
459
{ "exit_value", JSON_NUMBER, json_store_exit_value },
460
{ "iolog_file", JSON_STRING, json_store_iolog_file },
461
{ "iolog_path", JSON_STRING, json_store_iolog_path },
462
{ "iolog_offset", JSON_OBJECT, json_store_iolog_offset },
463
{ "lines", JSON_NUMBER, json_store_lines },
464
{ "peeraddr", JSON_STRING, json_store_peeraddr },
465
{ "run_time", JSON_OBJECT, json_store_run_time },
466
{ "runargv", JSON_ARRAY, json_store_runargv },
467
{ "runenv", JSON_ARRAY, json_store_runenv },
468
{ "runenv_override", JSON_ARRAY, json_store_runenv_override },
469
{ "rungid", JSON_ID, json_store_rungid },
470
{ "rungroup", JSON_STRING, json_store_rungroup },
471
{ "runuid", JSON_ID, json_store_runuid },
472
{ "runuser", JSON_STRING, json_store_runuser },
473
{ "runchroot", JSON_STRING, json_store_runchroot },
474
{ "runcwd", JSON_STRING, json_store_runcwd },
475
{ "source", JSON_STRING, json_store_source },
476
{ "signal", JSON_STRING, json_store_signal },
477
{ "submitcwd", JSON_STRING, json_store_submitcwd },
478
{ "submitenv", JSON_ARRAY, json_store_submitenv },
479
{ "submithost", JSON_STRING, json_store_submithost },
480
{ "submitgroup", JSON_STRING, json_store_submitgroup },
481
{ "submituser", JSON_STRING, json_store_submituser },
482
{ "timestamp", JSON_OBJECT, json_store_timestamp },
483
{ "ttyname", JSON_STRING, json_store_ttyname },
484
{ "uuid", JSON_STRING, json_store_uuid },
485
{ NULL }
486
};
487
488
static struct json_item *
489
new_json_item(enum json_value_type type, char *name, unsigned int lineno)
490
{
491
struct json_item *item;
492
debug_decl(new_json_item, SUDO_DEBUG_UTIL);
493
494
if ((item = malloc(sizeof(*item))) == NULL) {
495
sudo_warnx(U_("%s: %s"), __func__,
496
U_("unable to allocate memory"));
497
debug_return_ptr(NULL);
498
}
499
item->name = name;
500
item->type = type;
501
item->lineno = lineno;
502
503
debug_return_ptr(item);
504
}
505
506
static char *
507
json_parse_string(char **strp)
508
{
509
char *dst, *end, *ret, *src = *strp + 1;
510
size_t len;
511
debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
512
513
for (end = src; *end != '"' && *end != '\0'; end++) {
514
if (end[0] == '\\' && end[1] == '"')
515
end++;
516
}
517
if (*end != '"') {
518
sudo_warnx("%s", U_("missing double quote in name"));
519
debug_return_str(NULL);
520
}
521
len = (size_t)(end - src);
522
523
/* Copy string, flattening escaped chars. */
524
dst = ret = malloc(len + 1);
525
if (dst == NULL) {
526
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
527
debug_return_str(NULL);
528
}
529
while (src < end) {
530
int ch = *src++;
531
if (ch == '\\') {
532
switch (*src) {
533
case 'b':
534
ch = '\b';
535
break;
536
case 'f':
537
ch = '\f';
538
break;
539
case 'n':
540
ch = '\n';
541
break;
542
case 'r':
543
ch = '\r';
544
break;
545
case 't':
546
ch = '\t';
547
break;
548
case 'u':
549
/* Only currently handles 8-bit ASCII. */
550
if (src[1] == '0' && src[2] == '0') {
551
ch = sudo_hexchar(&src[3]);
552
if (ch != -1) {
553
src += 4;
554
break;
555
}
556
}
557
/* Not in \u00XX format. */
558
FALLTHROUGH;
559
case '"':
560
case '\\':
561
default:
562
/* Note: a bare \ at the end of a string will be removed. */
563
ch = *src;
564
break;
565
}
566
src++;
567
}
568
*dst++ = (char)ch;
569
}
570
*dst = '\0';
571
572
/* Trim trailing whitespace. */
573
do {
574
end++;
575
} while (isspace((unsigned char)*end));
576
*strp = end;
577
578
debug_return_str(ret);
579
}
580
581
static void
582
free_json_items(struct json_item_list *items)
583
{
584
struct json_item *item;
585
debug_decl(free_json_items, SUDO_DEBUG_UTIL);
586
587
while ((item = TAILQ_FIRST(items)) != NULL) {
588
TAILQ_REMOVE(items, item, entries);
589
switch (item->type) {
590
case JSON_STRING:
591
free(item->u.string);
592
break;
593
case JSON_ARRAY:
594
case JSON_OBJECT:
595
free_json_items(&item->u.child.items);
596
break;
597
case JSON_ID:
598
case JSON_NUMBER:
599
case JSON_BOOL:
600
case JSON_NULL:
601
/* Nothing to free. */
602
break;
603
default:
604
sudo_warnx("%s: internal error, invalid JSON type %d",
605
__func__, item->type);
606
break;
607
}
608
free(item->name);
609
free(item);
610
}
611
612
debug_return;
613
}
614
615
void
616
eventlog_json_free(struct eventlog_json_object *root)
617
{
618
debug_decl(eventlog_json_free, SUDO_DEBUG_UTIL);
619
if (root != NULL) {
620
free_json_items(&root->items);
621
free(root);
622
}
623
debug_return;
624
}
625
626
bool
627
eventlog_json_parse(struct eventlog_json_object *object, struct eventlog *evlog)
628
{
629
struct json_item *item;
630
bool ret = false;
631
debug_decl(eventlog_json_parse, SUDO_DEBUG_UTIL);
632
633
/* First object holds all the actual data. */
634
item = TAILQ_FIRST(&object->items);
635
if (item == NULL) {
636
sudo_warnx("%s", U_("missing JSON_OBJECT"));
637
goto done;
638
}
639
if (item->type != JSON_OBJECT) {
640
sudo_warnx(U_("expected JSON_OBJECT, got %d"), item->type);
641
goto done;
642
}
643
object = &item->u.child;
644
645
TAILQ_FOREACH(item, &object->items, entries) {
646
struct evlog_json_key *key;
647
648
/* expecting key:value pairs */
649
if (item->name == NULL) {
650
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
651
"%s: missing object name", __func__);
652
goto done;
653
}
654
655
/* lookup name */
656
for (key = evlog_json_keys; key->name != NULL; key++) {
657
if (strcmp(item->name, key->name) == 0)
658
break;
659
}
660
if (key->name == NULL) {
661
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
662
"%s: unknown key %s", __func__, item->name);
663
} else if (key->type != item->type &&
664
(key->type != JSON_ID || item->type != JSON_NUMBER)) {
665
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
666
"%s: key mismatch %s type %d, expected %d", __func__,
667
item->name, item->type, key->type);
668
goto done;
669
} else {
670
/* Matched name and type. */
671
if (!key->setter(item, evlog)) {
672
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
673
"unable to store %s", key->name);
674
goto done;
675
}
676
}
677
}
678
679
/*
680
* iolog_file must be a substring of iolog_path.
681
*/
682
if (iolog_file != NULL && evlog->iolog_path != NULL) {
683
const size_t filelen = strlen(iolog_file);
684
const size_t pathlen = strlen(evlog->iolog_path);
685
if (filelen <= pathlen) {
686
const char *cp = &evlog->iolog_path[pathlen - filelen];
687
if (strcmp(cp, iolog_file) == 0) {
688
evlog->iolog_file = cp;
689
}
690
}
691
}
692
693
ret = true;
694
695
done:
696
free(iolog_file);
697
iolog_file = NULL;
698
699
debug_return_bool(ret);
700
}
701
702
static bool
703
json_insert_bool(struct json_item_list *items, char *name, bool value,
704
unsigned int lineno)
705
{
706
struct json_item *item;
707
debug_decl(json_insert_bool, SUDO_DEBUG_UTIL);
708
709
if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL)
710
debug_return_bool(false);
711
item->u.boolean = value;
712
TAILQ_INSERT_TAIL(items, item, entries);
713
714
debug_return_bool(true);
715
}
716
717
static bool
718
json_insert_null(struct json_item_list *items, char *name, unsigned int lineno)
719
{
720
struct json_item *item;
721
debug_decl(json_insert_null, SUDO_DEBUG_UTIL);
722
723
if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL)
724
debug_return_bool(false);
725
TAILQ_INSERT_TAIL(items, item, entries);
726
727
debug_return_bool(true);
728
}
729
730
static bool
731
json_insert_num(struct json_item_list *items, char *name, long long value,
732
unsigned int lineno)
733
{
734
struct json_item *item;
735
debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
736
737
if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
738
debug_return_bool(false);
739
item->u.number = value;
740
TAILQ_INSERT_TAIL(items, item, entries);
741
742
debug_return_bool(true);
743
}
744
745
static bool
746
json_insert_str(struct json_item_list *items, char *name, char **strp,
747
unsigned int lineno)
748
{
749
struct json_item *item;
750
debug_decl(json_insert_str, SUDO_DEBUG_UTIL);
751
752
if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL)
753
debug_return_bool(false);
754
item->u.string = json_parse_string(strp);
755
if (item->u.string == NULL) {
756
free(item);
757
debug_return_bool(false);
758
}
759
TAILQ_INSERT_TAIL(items, item, entries);
760
761
debug_return_bool(true);
762
}
763
764
static struct eventlog_json_object *
765
json_stack_push(struct json_stack *stack, struct json_item_list *items,
766
struct eventlog_json_object *frame, enum json_value_type type, char *name,
767
unsigned int lineno)
768
{
769
struct json_item *item;
770
debug_decl(json_stack_push, SUDO_DEBUG_UTIL);
771
772
/* We limit the stack size rather than expanding it. */
773
if (stack->depth >= stack->maxdepth) {
774
sudo_warnx(U_("json stack exhausted (max %u frames)"), stack->maxdepth);
775
debug_return_ptr(NULL);
776
}
777
778
/* Allocate a new item and insert it into the list. */
779
if ((item = new_json_item(type, name, lineno)) == NULL)
780
debug_return_ptr(NULL);
781
TAILQ_INIT(&item->u.child.items);
782
item->u.child.parent = item;
783
TAILQ_INSERT_TAIL(items, item, entries);
784
785
/* Push the current frame onto the stack (depth check performed above). */
786
stack->frames[stack->depth++] = frame;
787
788
/* Return the new frame */
789
debug_return_ptr(&item->u.child);
790
}
791
792
/* Only expect a value if a name is defined or we are in an array. */
793
#define expect_value (name != NULL || (frame->parent != NULL && frame->parent->type == JSON_ARRAY))
794
795
struct eventlog_json_object *
796
eventlog_json_read(FILE *fp, const char *filename)
797
{
798
struct eventlog_json_object *frame, *root;
799
struct json_stack stack = JSON_STACK_INTIALIZER(stack);
800
unsigned int lineno = 0;
801
char *name = NULL;
802
char *cp, *line = NULL;
803
size_t len, linesize = 0;
804
ssize_t linelen;
805
bool saw_comma = false;
806
long long num;
807
char ch;
808
debug_decl(eventlog_json_read, SUDO_DEBUG_UTIL);
809
810
root = malloc(sizeof(*root));
811
if (root == NULL)
812
goto bad;
813
814
root->parent = NULL;
815
TAILQ_INIT(&root->items);
816
817
frame = root;
818
while ((linelen = getdelim(&line, &linesize, '\n', fp)) != -1) {
819
char *ep = line + linelen - 1;
820
cp = line;
821
822
lineno++;
823
824
/* Trim trailing whitespace. */
825
while (ep > cp && isspace((unsigned char)*ep))
826
ep--;
827
ep[1] = '\0';
828
829
for (;;) {
830
const char *errstr;
831
832
/* Trim leading whitespace, skip blank lines. */
833
while (isspace((unsigned char)*cp))
834
cp++;
835
836
/* Check for comma separator and strip it out. */
837
if (*cp == ',') {
838
saw_comma = true;
839
cp++;
840
while (isspace((unsigned char)*cp))
841
cp++;
842
}
843
844
/* End of line? */
845
if (*cp == '\0')
846
break;
847
848
switch (*cp) {
849
case '{':
850
if (name == NULL && frame->parent != NULL) {
851
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
852
U_("objects must consist of name:value pairs"));
853
goto bad;
854
}
855
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
856
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
857
U_("missing separator between values"));
858
goto bad;
859
}
860
cp++;
861
saw_comma = false;
862
frame = json_stack_push(&stack, &frame->items, frame,
863
JSON_OBJECT, name, lineno);
864
if (frame == NULL)
865
goto bad;
866
name = NULL;
867
break;
868
case '}':
869
if (stack.depth == 0 || frame->parent == NULL ||
870
frame->parent->type != JSON_OBJECT) {
871
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
872
U_("unmatched close brace"));
873
goto bad;
874
}
875
cp++;
876
frame = stack.frames[--stack.depth];
877
saw_comma = false;
878
break;
879
case '[':
880
if (frame->parent == NULL) {
881
/* Must have an enclosing object. */
882
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
883
U_("unexpected array"));
884
goto bad;
885
}
886
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
887
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
888
U_("missing separator between values"));
889
goto bad;
890
}
891
cp++;
892
saw_comma = false;
893
frame = json_stack_push(&stack, &frame->items, frame,
894
JSON_ARRAY, name, lineno);
895
if (frame == NULL)
896
goto bad;
897
name = NULL;
898
break;
899
case ']':
900
if (stack.depth == 0 || frame->parent == NULL ||
901
frame->parent->type != JSON_ARRAY) {
902
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
903
U_("unmatched close bracket"));
904
goto bad;
905
}
906
cp++;
907
frame = stack.frames[--stack.depth];
908
saw_comma = false;
909
break;
910
case '"':
911
if (frame->parent == NULL) {
912
/* Must have an enclosing object. */
913
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
914
U_("unexpected string"));
915
goto bad;
916
}
917
918
if (!expect_value) {
919
/* Parse "name": */
920
if ((name = json_parse_string(&cp)) == NULL)
921
goto bad;
922
/* TODO: allow colon on next line? */
923
if (*cp != ':') {
924
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
925
U_("missing colon after name"));
926
goto bad;
927
}
928
cp++;
929
} else {
930
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
931
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
932
U_("missing separator between values"));
933
goto bad;
934
}
935
saw_comma = false;
936
if (!json_insert_str(&frame->items, name, &cp, lineno))
937
goto bad;
938
name = NULL;
939
}
940
break;
941
case 't':
942
if (strncmp(cp, "true", sizeof("true") - 1) != 0)
943
goto parse_error;
944
if (!expect_value) {
945
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
946
U_("unexpected boolean"));
947
goto bad;
948
}
949
cp += sizeof("true") - 1;
950
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
951
goto parse_error;
952
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
953
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
954
U_("missing separator between values"));
955
goto bad;
956
}
957
saw_comma = false;
958
959
if (!json_insert_bool(&frame->items, name, true, lineno))
960
goto bad;
961
name = NULL;
962
break;
963
case 'f':
964
if (strncmp(cp, "false", sizeof("false") - 1) != 0)
965
goto parse_error;
966
if (!expect_value) {
967
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
968
U_("unexpected boolean"));
969
goto bad;
970
}
971
cp += sizeof("false") - 1;
972
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
973
goto parse_error;
974
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
975
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
976
U_("missing separator between values"));
977
goto bad;
978
}
979
saw_comma = false;
980
981
if (!json_insert_bool(&frame->items, name, false, lineno))
982
goto bad;
983
name = NULL;
984
break;
985
case 'n':
986
if (strncmp(cp, "null", sizeof("null") - 1) != 0)
987
goto parse_error;
988
if (!expect_value) {
989
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
990
U_("unexpected null"));
991
goto bad;
992
}
993
cp += sizeof("null") - 1;
994
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
995
goto parse_error;
996
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
997
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
998
U_("missing separator between values"));
999
goto bad;
1000
}
1001
saw_comma = false;
1002
1003
if (!json_insert_null(&frame->items, name, lineno))
1004
goto bad;
1005
name = NULL;
1006
break;
1007
case '+': case '-': case '0': case '1': case '2': case '3':
1008
case '4': case '5': case '6': case '7': case '8': case '9':
1009
if (!expect_value) {
1010
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1011
U_("unexpected number"));
1012
goto bad;
1013
}
1014
/* XXX - strtonumx() would be simpler here. */
1015
len = strcspn(cp, " \f\n\r\t\v,");
1016
ch = cp[len];
1017
cp[len] = '\0';
1018
if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
1019
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1020
U_("missing separator between values"));
1021
goto bad;
1022
}
1023
saw_comma = false;
1024
num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
1025
if (errstr != NULL) {
1026
sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - line,
1027
cp, U_(errstr));
1028
goto bad;
1029
}
1030
cp += len;
1031
*cp = ch;
1032
1033
if (!json_insert_num(&frame->items, name, num, lineno))
1034
goto bad;
1035
name = NULL;
1036
break;
1037
default:
1038
goto parse_error;
1039
}
1040
}
1041
}
1042
if (stack.depth != 0) {
1043
frame = stack.frames[stack.depth - 1];
1044
if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) {
1045
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1046
U_("unmatched close brace"));
1047
} else {
1048
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1049
U_("unmatched close bracket"));
1050
}
1051
goto bad;
1052
}
1053
1054
goto done;
1055
1056
parse_error:
1057
sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, U_("parse error"));
1058
bad:
1059
eventlog_json_free(root);
1060
root = NULL;
1061
done:
1062
free(line);
1063
free(name);
1064
1065
debug_return_ptr(root);
1066
}
1067
1068