Coverage Report

Created: 2025-10-28 06:14

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.21k
#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
121
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
60
121
      "tty cols %lld: out of range", item->u.number);
61
121
  evlog->columns = 0;
62
121
  debug_return_bool(false);
63
121
    }
64
65
263
    evlog->columns = (int)item->u.number;
66
263
    debug_return_bool(true);
67
263
}
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
115
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
100
115
      "exit value %lld: out of range", item->u.number);
101
115
  evlog->exit_value = -1;
102
115
  debug_return_bool(false);
103
115
    }
104
105
253
    evlog->exit_value = (int)item->u.number;
106
253
    debug_return_bool(true);
107
253
}
108
109
static bool
110
json_store_iolog_file(struct json_item *item, struct eventlog *evlog)
111
367
{
112
367
    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
367
    free(iolog_file);
116
367
    iolog_file = item->u.string;
117
367
    item->u.string = NULL;
118
367
    debug_return_bool(true);
119
367
}
120
121
static bool
122
json_store_iolog_path(struct json_item *item, struct eventlog *evlog)
123
366
{
124
366
    debug_decl(json_store_iolog_path, SUDO_DEBUG_UTIL);
125
126
366
    free(evlog->iolog_path);
127
366
    evlog->iolog_path = item->u.string;
128
366
    item->u.string = NULL;
129
366
    debug_return_bool(true);
130
366
}
131
132
static bool
133
json_store_lines(struct json_item *item, struct eventlog *evlog)
134
382
{
135
382
    debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
136
137
382
    if (item->u.number < 1 || item->u.number > INT_MAX) {
138
118
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
139
118
      "tty lines %lld: out of range", item->u.number);
140
118
  evlog->lines = 0;
141
118
  debug_return_bool(false);
142
118
    }
143
144
264
    evlog->lines = (int)item->u.number;
145
264
    debug_return_bool(true);
146
264
}
147
148
static bool
149
json_store_peeraddr(struct json_item *item, struct eventlog *evlog)
150
198
{
151
198
    debug_decl(json_store_peeraddr, SUDO_DEBUG_UTIL);
152
153
198
    free(evlog->peeraddr);
154
198
    evlog->peeraddr = item->u.string;
155
198
    item->u.string = NULL;
156
198
    debug_return_bool(true);
157
198
}
158
159
static char **
160
json_array_to_strvec(struct eventlog_json_object *array)
161
2.67k
{
162
2.67k
    struct json_item *item;
163
2.67k
    size_t len = 0;
164
2.67k
    char **ret;
165
2.67k
    debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
166
167
2.77k
    TAILQ_FOREACH(item, &array->items, entries) {
168
  /* Can only convert arrays of string. */
169
2.77k
  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.77k
  if (++len == INT_MAX) {
175
0
      sudo_warnx("%s", U_("JSON_ARRAY too large"));
176
0
      debug_return_ptr(NULL);
177
0
  }
178
2.77k
    }
179
2.67k
    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.67k
    len = 0;
184
2.77k
    TAILQ_FOREACH(item, &array->items, entries) {
185
2.77k
  ret[len++] = item->u.string;
186
2.77k
  item->u.string = NULL;
187
2.77k
    }
188
2.67k
    ret[len] = NULL;
189
190
2.67k
    debug_return_ptr(ret);
191
2.67k
}
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
813
{
228
813
    size_t i;
229
813
    debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
230
231
813
    if (evlog->runenv != NULL) {
232
1.34k
  for (i = 0; evlog->runenv[i] != NULL; i++)
233
582
      free(evlog->runenv[i]);
234
762
  free(evlog->runenv);
235
762
    }
236
813
    evlog->runenv = json_array_to_strvec(&item->u.child);
237
238
813
    debug_return_bool(evlog->runenv != NULL);
239
813
}
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
194
{
289
194
    debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
290
291
194
    free(evlog->runuser);
292
194
    evlog->runuser = item->u.string;
293
194
    item->u.string = NULL;
294
194
    debug_return_bool(true);
295
194
}
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
210
{
355
210
    debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
356
357
210
    free(evlog->submithost);
358
210
    evlog->submithost = item->u.string;
359
210
    item->u.string = NULL;
360
210
    debug_return_bool(true);
361
210
}
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
3.95k
    TAILQ_FOREACH(item, &object->items, entries) {
393
3.95k
  if (item->type != JSON_NUMBER)
394
1.74k
      continue;
395
2.21k
  if (strcmp(item->name, "seconds") == 0) {
396
582
      ts->tv_sec = (time_t)item->u.number;
397
582
      continue;
398
582
  }
399
1.63k
  if (strcmp(item->name, "nanoseconds") == 0) {
400
582
      ts->tv_nsec = (long)item->u.number;
401
582
      continue;
402
582
  }
403
1.63k
    }
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
580
{
410
580
    return json_store_timespec(item, &evlog->iolog_offset);
411
580
}
412
413
static bool
414
json_store_run_time(struct json_item *item, struct eventlog *evlog)
415
583
{
416
583
    return json_store_timespec(item, &evlog->run_time);
417
583
}
418
419
static bool
420
json_store_timestamp(struct json_item *item, struct eventlog *evlog)
421
581
{
422
581
    return json_store_timespec(item, &evlog->event_time);
423
581
}
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.6k
{
509
22.6k
    char *dst, *end, *ret, *src = *strp + 1;
510
22.6k
    size_t len;
511
22.6k
    debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
512
513
4.64M
    for (end = src; *end != '"' && *end != '\0'; end++) {
514
4.62M
  if (end[0] == '\\' && end[1] == '"')
515
356
      end++;
516
4.62M
    }
517
22.6k
    if (*end != '"') {
518
85
  sudo_warnx("%s", U_("missing double quote in name"));
519
85
  debug_return_str(NULL);
520
85
    }
521
22.6k
    len = (size_t)(end - src);
522
523
    /* Copy string, flattening escaped chars. */
524
22.6k
    dst = ret = malloc(len + 1);
525
22.6k
    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
3.98k
      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
200
      case 'r':
543
200
    ch = '\r';
544
200
    break;
545
194
      case 't':
546
194
    ch = '\t';
547
194
    break;
548
2.78k
      case 'u':
549
    /* Only currently handles 8-bit ASCII. */
550
2.78k
    if (src[1] == '0' && src[2] == '0') {
551
1.95k
        ch = sudo_hexchar(&src[3]);
552
1.95k
        if (ch != -1) {
553
264
      src += 4;
554
264
      break;
555
264
        }
556
1.95k
    }
557
    /* Not in \u00XX format. */
558
2.52k
    FALLTHROUGH;
559
2.68k
      case '"':
560
2.69k
      case '\\':
561
2.74k
      default:
562
    /* Note: a bare \ at the end of a string will be removed. */
563
2.74k
    ch = *src;
564
2.74k
    break;
565
3.98k
      }
566
3.98k
      src++;
567
3.98k
  }
568
4.55M
  *dst++ = (char)ch;
569
4.55M
    }
570
22.6k
    *dst = '\0';
571
572
    /* Trim trailing whitespace. */
573
22.8k
    do {
574
22.8k
  end++;
575
22.8k
    } while (isspace((unsigned char)*end));
576
22.6k
    *strp = end;
577
578
22.6k
    debug_return_str(ret);
579
22.6k
}
580
581
static void
582
free_json_items(struct json_item_list *items)
583
17.7k
{
584
17.7k
    struct json_item *item;
585
17.7k
    debug_decl(free_json_items, SUDO_DEBUG_UTIL);
586
587
52.3k
    while ((item = TAILQ_FIRST(items)) != NULL) {
588
34.5k
  TAILQ_REMOVE(items, item, entries);
589
34.5k
  switch (item->type) {
590
7.80k
  case JSON_STRING:
591
7.80k
      free(item->u.string);
592
7.80k
      break;
593
8.32k
  case JSON_ARRAY:
594
14.5k
  case JSON_OBJECT:
595
14.5k
      free_json_items(&item->u.child.items);
596
14.5k
      break;
597
0
  case JSON_ID:
598
8.26k
  case JSON_NUMBER:
599
10.8k
  case JSON_BOOL:
600
12.2k
  case JSON_NULL:
601
      /* Nothing to free. */
602
12.2k
      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.5k
  }
608
34.5k
  free(item->name);
609
34.5k
  free(item);
610
34.5k
    }
611
612
17.7k
    debug_return;
613
17.7k
}
614
615
void
616
eventlog_json_free(struct eventlog_json_object *root)
617
3.21k
{
618
3.21k
    debug_decl(eventlog_json_free, SUDO_DEBUG_UTIL);
619
3.21k
    if (root != NULL) {
620
3.21k
  free_json_items(&root->items);
621
3.21k
  free(root);
622
3.21k
    }
623
3.21k
    debug_return;
624
3.21k
}
625
626
bool
627
eventlog_json_parse(struct eventlog_json_object *object, struct eventlog *evlog)
628
1.89k
{
629
1.89k
    struct json_item *item;
630
1.89k
    bool ret = false;
631
1.89k
    debug_decl(eventlog_json_parse, SUDO_DEBUG_UTIL);
632
633
    /* First object holds all the actual data. */
634
1.89k
    item = TAILQ_FIRST(&object->items);
635
1.89k
    if (item == NULL) {
636
15
  sudo_warnx("%s", U_("missing JSON_OBJECT"));
637
15
  goto done;
638
15
    }
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
13
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
651
13
    "%s: missing object name", __func__);
652
13
      goto done;
653
13
  }
654
655
  /* lookup name */
656
154k
  for (key = evlog_json_keys; key->name != NULL; key++) {
657
154k
      if (strcmp(item->name, key->name) == 0)
658
9.83k
    break;
659
154k
  }
660
10.3k
  if (key->name == NULL) {
661
499
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
662
499
    "%s: unknown key %s", __func__, item->name);
663
9.83k
  } else if (key->type != item->type &&
664
398
    (key->type != JSON_ID || item->type != JSON_NUMBER)) {
665
10
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
666
10
    "%s: key mismatch %s type %d, expected %d", __func__,
667
10
    item->name, item->type, key->type);
668
10
      goto done;
669
9.82k
  } else {
670
      /* Matched name and type. */
671
9.82k
      if (!key->setter(item, evlog)) {
672
388
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
673
388
        "unable to store %s", key->name);
674
388
    goto done;
675
388
      }
676
9.82k
  }
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
172
  const size_t filelen = strlen(iolog_file);
684
172
  const size_t pathlen = strlen(evlog->iolog_path);
685
172
  if (filelen <= pathlen) {
686
140
      const char *cp = &evlog->iolog_path[pathlen - filelen];
687
140
      if (strcmp(cp, iolog_file) == 0) {
688
2
    evlog->iolog_file = cp;
689
2
      }
690
140
  }
691
172
    }
692
693
1.46k
    ret = true;
694
695
1.89k
done:
696
1.89k
    free(iolog_file);
697
1.89k
    iolog_file = NULL;
698
699
1.89k
    debug_return_bool(ret);
700
1.89k
}
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.26k
{
734
8.26k
    struct json_item *item;
735
8.26k
    debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
736
737
8.26k
    if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
738
0
  debug_return_bool(false);
739
8.26k
    item->u.number = value;
740
8.26k
    TAILQ_INSERT_TAIL(items, item, entries);
741
742
8.26k
    debug_return_bool(true);
743
8.26k
}
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
28
  free(item);
757
28
  debug_return_bool(false);
758
28
    }
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.5k
{
769
14.5k
    struct json_item *item;
770
14.5k
    debug_decl(json_stack_push, SUDO_DEBUG_UTIL);
771
772
    /* We limit the stack size rather than expanding it. */
773
14.5k
    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.5k
    if ((item = new_json_item(type, name, lineno)) == NULL)
780
0
  debug_return_ptr(NULL);
781
14.5k
    TAILQ_INIT(&item->u.child.items);
782
14.5k
    item->u.child.parent = item;
783
14.5k
    TAILQ_INSERT_TAIL(items, item, entries);
784
785
    /* Push the current frame onto the stack (depth check performed above). */
786
14.5k
    stack->frames[stack->depth++] = frame;
787
788
    /* Return the new frame */
789
14.5k
    debug_return_ptr(&item->u.child);
790
14.5k
}
791
792
/* Only expect a value if a name is defined or we are in an array. */
793
35.2k
#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.21k
{
798
3.21k
    struct eventlog_json_object *frame, *root;
799
3.21k
    struct json_stack stack = JSON_STACK_INTIALIZER(stack);
800
3.21k
    unsigned int lineno = 0;
801
3.21k
    char *name = NULL;
802
3.21k
    char *cp, *line = NULL;
803
3.21k
    size_t len, linesize = 0;
804
3.21k
    ssize_t linelen;
805
3.21k
    bool saw_comma = false;
806
3.21k
    long long num;
807
3.21k
    char ch;
808
3.21k
    debug_decl(eventlog_json_read, SUDO_DEBUG_UTIL);
809
810
3.21k
    root = malloc(sizeof(*root));
811
3.21k
    if (root == NULL)
812
0
  goto bad;
813
814
3.21k
    root->parent = NULL;
815
3.21k
    TAILQ_INIT(&root->items);
816
817
3.21k
    frame = root;
818
6.73k
    while ((linelen = getdelim(&line, &linesize, '\n', fp)) != -1) {
819
4.21k
  char *ep = line + linelen - 1;
820
4.21k
  cp = line;
821
822
4.21k
  lineno++;
823
824
  /* Trim trailing whitespace. */
825
5.09k
  while (ep > cp && isspace((unsigned char)*ep))
826
874
      ep--;
827
4.21k
  ep[1] = '\0';
828
829
63.4k
  for (;;) {
830
63.4k
      const char *errstr;
831
832
      /* Trim leading whitespace, skip blank lines. */
833
63.4k
      while (isspace((unsigned char)*cp))
834
1.45k
    cp++;
835
836
      /* Check for comma separator and strip it out. */
837
63.4k
      if (*cp == ',') {
838
23.2k
    saw_comma = true;
839
23.2k
    cp++;
840
23.2k
    while (isspace((unsigned char)*cp))
841
320
        cp++;
842
23.2k
      }
843
844
      /* End of line? */
845
63.4k
      if (*cp == '\0')
846
3.51k
    break;
847
848
59.9k
      switch (*cp) {
849
6.24k
      case '{':
850
6.24k
    if (name == NULL && frame->parent != NULL) {
851
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
852
2
      U_("objects must consist of name:value pairs"));
853
2
        goto bad;
854
2
    }
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.23k
    cp++;
861
6.23k
    saw_comma = false;
862
6.23k
    frame = json_stack_push(&stack, &frame->items, frame,
863
6.23k
        JSON_OBJECT, name, lineno);
864
6.23k
    if (frame == NULL)
865
3
        goto bad;
866
6.22k
    name = NULL;
867
6.22k
    break;
868
4.59k
      case '}':
869
4.59k
    if (stack.depth == 0 || frame->parent == NULL ||
870
4.59k
      frame->parent->type != JSON_OBJECT) {
871
8
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
872
8
      U_("unmatched close brace"));
873
8
        goto bad;
874
8
    }
875
4.58k
    cp++;
876
4.58k
    frame = stack.frames[--stack.depth];
877
4.58k
    saw_comma = false;
878
4.58k
    break;
879
8.32k
      case '[':
880
8.32k
    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.32k
    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.32k
    cp++;
892
8.32k
    saw_comma = false;
893
8.32k
    frame = json_stack_push(&stack, &frame->items, frame,
894
8.32k
        JSON_ARRAY, name, lineno);
895
8.32k
    if (frame == NULL)
896
4
        goto bad;
897
8.32k
    name = NULL;
898
8.32k
    break;
899
5.44k
      case ']':
900
5.44k
    if (stack.depth == 0 || frame->parent == NULL ||
901
5.44k
      frame->parent->type != JSON_ARRAY) {
902
3
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
903
3
      U_("unmatched close bracket"));
904
3
        goto bad;
905
3
    }
906
5.44k
    cp++;
907
5.44k
    frame = stack.frames[--stack.depth];
908
5.44k
    saw_comma = false;
909
5.44k
    break;
910
22.7k
      case '"':
911
22.7k
    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.7k
    if (!expect_value) {
919
        /* Parse "name": */
920
14.8k
        if ((name = json_parse_string(&cp)) == NULL)
921
57
      goto bad;
922
        /* TODO: allow colon on next line? */
923
14.8k
        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.6k
        cp++;
929
14.6k
    } else {
930
7.84k
        if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
931
15
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
932
15
          U_("missing separator between values"));
933
15
      goto bad;
934
15
        }
935
7.83k
        saw_comma = false;
936
7.83k
        if (!json_insert_str(&frame->items, name, &cp, lineno))
937
28
      goto bad;
938
7.80k
        name = NULL;
939
7.80k
    }
940
22.4k
    break;
941
22.4k
      case 't':
942
1.46k
    if (strncmp(cp, "true", sizeof("true") - 1) != 0)
943
27
        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.43k
    cp += sizeof("true") - 1;
950
1.43k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
951
12
        goto parse_error;
952
1.42k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
953
15
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
954
15
      U_("missing separator between values"));
955
15
        goto bad;
956
15
    }
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.28k
      case 'f':
964
1.28k
    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
13
        goto parse_error;
974
1.23k
    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
11
        goto parse_error;
996
1.36k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
997
16
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
998
16
      U_("missing separator between values"));
999
16
        goto bad;
1000
16
    }
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
4.91k
      case '+': case '-': case '0': case '1': case '2': case '3':
1008
8.43k
      case '4': case '5': case '6': case '7': case '8': case '9':
1009
8.43k
    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.42k
    len = strcspn(cp, " \f\n\r\t\v,");
1016
8.42k
    ch = cp[len];
1017
8.42k
    cp[len] = '\0';
1018
8.42k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
1019
3
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
1020
3
      U_("missing separator between values"));
1021
3
        goto bad;
1022
3
    }
1023
8.41k
    saw_comma = false;
1024
8.41k
    num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
1025
8.41k
    if (errstr != NULL) {
1026
158
        sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - line,
1027
158
      cp, U_(errstr));
1028
158
        goto bad;
1029
158
    }
1030
8.26k
    cp += len;
1031
8.26k
    *cp = ch;
1032
1033
8.26k
    if (!json_insert_num(&frame->items, name, num, lineno))
1034
0
        goto bad;
1035
8.26k
    name = NULL;
1036
8.26k
    break;
1037
23
      default:
1038
23
    goto parse_error;
1039
59.9k
      }
1040
59.9k
  }
1041
4.21k
    }
1042
2.51k
    if (stack.depth != 0) {
1043
623
  frame = stack.frames[stack.depth - 1];
1044
623
  if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) {
1045
550
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1046
550
    U_("unmatched close brace"));
1047
550
  } else {
1048
73
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1049
73
    U_("unmatched close bracket"));
1050
73
  }
1051
623
  goto bad;
1052
623
    }
1053
1054
1.89k
    goto done;
1055
1056
1.89k
parse_error:
1057
151
    sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, U_("parse error"));
1058
1.32k
bad:
1059
1.32k
    eventlog_json_free(root);
1060
1.32k
    root = NULL;
1061
3.21k
done:
1062
3.21k
    free(line);
1063
3.21k
    free(name);
1064
1065
3.21k
    debug_return_ptr(root);
1066
3.21k
}