Coverage Report

Created: 2025-10-13 06:08

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