Coverage Report

Created: 2025-10-10 07:09

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.20k
#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
381
{
56
381
    debug_decl(json_store_columns, SUDO_DEBUG_UTIL);
57
58
381
    if (item->u.number < 1 || item->u.number > INT_MAX) {
59
116
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
60
116
      "tty cols %lld: out of range", item->u.number);
61
116
  evlog->columns = 0;
62
116
  debug_return_bool(false);
63
116
    }
64
65
265
    evlog->columns = (int)item->u.number;
66
265
    debug_return_bool(true);
67
265
}
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
365
{
96
365
    debug_decl(json_store_exit_value, SUDO_DEBUG_UTIL);
97
98
365
    if (item->u.number < 0 || item->u.number > INT_MAX) {
99
112
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
100
112
      "exit value %lld: out of range", item->u.number);
101
112
  evlog->exit_value = -1;
102
112
  debug_return_bool(false);
103
112
    }
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
377
{
135
377
    debug_decl(json_store_lines, SUDO_DEBUG_UTIL);
136
137
377
    if (item->u.number < 1 || item->u.number > INT_MAX) {
138
115
  sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
139
115
      "tty lines %lld: out of range", item->u.number);
140
115
  evlog->lines = 0;
141
115
  debug_return_bool(false);
142
115
    }
143
144
262
    evlog->lines = (int)item->u.number;
145
262
    debug_return_bool(true);
146
262
}
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.65k
{
162
2.65k
    struct json_item *item;
163
2.65k
    size_t len = 0;
164
2.65k
    char **ret;
165
2.65k
    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.65k
    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.65k
    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.65k
    ret[len] = NULL;
189
190
2.65k
    debug_return_ptr(ret);
191
2.65k
}
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
614
{
244
614
    size_t i;
245
614
    debug_decl(json_store_runenv_override, SUDO_DEBUG_UTIL);
246
247
614
    if (evlog->env_add != NULL) {
248
972
  for (i = 0; evlog->env_add[i] != NULL; i++)
249
396
      free(evlog->env_add[i]);
250
576
  free(evlog->env_add);
251
576
    }
252
614
    evlog->env_add = json_array_to_strvec(&item->u.child);
253
254
614
    debug_return_bool(evlog->env_add != NULL);
255
614
}
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
194
{
355
194
    debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
356
357
194
    free(evlog->submithost);
358
194
    evlog->submithost = item->u.string;
359
194
    item->u.string = NULL;
360
194
    debug_return_bool(true);
361
194
}
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.05k
    TAILQ_FOREACH(item, &object->items, entries) {
393
4.05k
  if (item->type != JSON_NUMBER)
394
1.76k
      continue;
395
2.29k
  if (strcmp(item->name, "seconds") == 0) {
396
582
      ts->tv_sec = (time_t)item->u.number;
397
582
      continue;
398
582
  }
399
1.71k
  if (strcmp(item->name, "nanoseconds") == 0) {
400
637
      ts->tv_nsec = (long)item->u.number;
401
637
      continue;
402
637
  }
403
1.71k
    }
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
581
{
410
581
    return json_store_timespec(item, &evlog->iolog_offset);
411
581
}
412
413
static bool
414
json_store_run_time(struct json_item *item, struct eventlog *evlog)
415
581
{
416
581
    return json_store_timespec(item, &evlog->run_time);
417
581
}
418
419
static bool
420
json_store_timestamp(struct json_item *item, struct eventlog *evlog)
421
580
{
422
580
    return json_store_timespec(item, &evlog->event_time);
423
580
}
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
36.5k
{
491
36.5k
    struct json_item *item;
492
36.5k
    debug_decl(new_json_item, SUDO_DEBUG_UTIL);
493
494
36.5k
    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
36.5k
    item->name = name;
500
36.5k
    item->type = type;
501
36.5k
    item->lineno = lineno;
502
503
36.5k
    debug_return_ptr(item);
504
36.5k
}
505
506
static char *
507
json_parse_string(char **strp)
508
24.4k
{
509
24.4k
    char *dst, *end, *ret, *src = *strp + 1;
510
24.4k
    size_t len;
511
24.4k
    debug_decl(json_parse_string, SUDO_DEBUG_UTIL);
512
513
4.75M
    for (end = src; *end != '"' && *end != '\0'; end++) {
514
4.72M
  if (end[0] == '\\' && end[1] == '"')
515
322
      end++;
516
4.72M
    }
517
24.4k
    if (*end != '"') {
518
85
  sudo_warnx("%s", U_("missing double quote in name"));
519
85
  debug_return_str(NULL);
520
85
    }
521
24.4k
    len = (size_t)(end - src);
522
523
    /* Copy string, flattening escaped chars. */
524
24.4k
    dst = ret = malloc(len + 1);
525
24.4k
    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.74M
    while (src < end) {
530
4.72M
  int ch = *src++;
531
4.72M
  if (ch == '\\') {
532
3.08k
      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
214
      case 'r':
543
214
    ch = '\r';
544
214
    break;
545
291
      case 't':
546
291
    ch = '\t';
547
291
    break;
548
1.59k
      case 'u':
549
    /* Only currently handles 8-bit ASCII. */
550
1.59k
    if (src[1] == '0' && src[2] == '0') {
551
974
        ch = sudo_hexchar(&src[3]);
552
974
        if (ch != -1) {
553
202
      src += 4;
554
202
      break;
555
202
        }
556
974
    }
557
    /* Not in \u00XX format. */
558
1.39k
    FALLTHROUGH;
559
1.52k
      case '"':
560
1.73k
      case '\\':
561
1.79k
      default:
562
    /* Note: a bare \ at the end of a string will be removed. */
563
1.79k
    ch = *src;
564
1.79k
    break;
565
3.08k
      }
566
3.08k
      src++;
567
3.08k
  }
568
4.72M
  *dst++ = (char)ch;
569
4.72M
    }
570
24.4k
    *dst = '\0';
571
572
    /* Trim trailing whitespace. */
573
24.6k
    do {
574
24.6k
  end++;
575
24.6k
    } while (isspace((unsigned char)*end));
576
24.4k
    *strp = end;
577
578
24.4k
    debug_return_str(ret);
579
24.4k
}
580
581
static void
582
free_json_items(struct json_item_list *items)
583
17.8k
{
584
17.8k
    struct json_item *item;
585
17.8k
    debug_decl(free_json_items, SUDO_DEBUG_UTIL);
586
587
54.3k
    while ((item = TAILQ_FIRST(items)) != NULL) {
588
36.4k
  TAILQ_REMOVE(items, item, entries);
589
36.4k
  switch (item->type) {
590
7.75k
  case JSON_STRING:
591
7.75k
      free(item->u.string);
592
7.75k
      break;
593
8.33k
  case JSON_ARRAY:
594
14.6k
  case JSON_OBJECT:
595
14.6k
      free_json_items(&item->u.child.items);
596
14.6k
      break;
597
0
  case JSON_ID:
598
10.0k
  case JSON_NUMBER:
599
12.7k
  case JSON_BOOL:
600
14.0k
  case JSON_NULL:
601
      /* Nothing to free. */
602
14.0k
      break;
603
0
  default:
604
0
      sudo_warnx("%s: internal error, invalid JSON type %d",
605
0
    __func__, item->type);
606
0
      break;
607
36.4k
  }
608
36.4k
  free(item->name);
609
36.4k
  free(item);
610
36.4k
    }
611
612
17.8k
    debug_return;
613
17.8k
}
614
615
void
616
eventlog_json_free(struct eventlog_json_object *root)
617
3.20k
{
618
3.20k
    debug_decl(eventlog_json_free, SUDO_DEBUG_UTIL);
619
3.20k
    if (root != NULL) {
620
3.20k
  free_json_items(&root->items);
621
3.20k
  free(root);
622
3.20k
    }
623
3.20k
    debug_return;
624
3.20k
}
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
15
  sudo_warnx("%s", U_("missing JSON_OBJECT"));
637
15
  goto done;
638
15
    }
639
1.86k
    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.86k
    object = &item->u.child;
644
645
10.2k
    TAILQ_FOREACH(item, &object->items, entries) {
646
10.2k
  struct evlog_json_key *key;
647
648
  /* expecting key:value pairs */
649
10.2k
  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
153k
      if (strcmp(item->name, key->name) == 0)
658
9.78k
    break;
659
153k
  }
660
10.2k
  if (key->name == NULL) {
661
505
      sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
662
505
    "%s: unknown key %s", __func__, item->name);
663
9.78k
  } 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.77k
  } else {
670
      /* Matched name and type. */
671
9.77k
      if (!key->setter(item, evlog)) {
672
377
    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
673
377
        "unable to store %s", key->name);
674
377
    goto done;
675
377
      }
676
9.77k
  }
677
10.2k
    }
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
141
      const char *cp = &evlog->iolog_path[pathlen - filelen];
687
141
      if (strcmp(cp, iolog_file) == 0) {
688
3
    evlog->iolog_file = cp;
689
3
      }
690
141
  }
691
172
    }
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.63k
{
706
2.63k
    struct json_item *item;
707
2.63k
    debug_decl(json_insert_bool, SUDO_DEBUG_UTIL);
708
709
2.63k
    if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL)
710
0
  debug_return_bool(false);
711
2.63k
    item->u.boolean = value;
712
2.63k
    TAILQ_INSERT_TAIL(items, item, entries);
713
714
2.63k
    debug_return_bool(true);
715
2.63k
}
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
10.0k
{
734
10.0k
    struct json_item *item;
735
10.0k
    debug_decl(json_insert_num, SUDO_DEBUG_UTIL);
736
737
10.0k
    if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL)
738
0
  debug_return_bool(false);
739
10.0k
    item->u.number = value;
740
10.0k
    TAILQ_INSERT_TAIL(items, item, entries);
741
742
10.0k
    debug_return_bool(true);
743
10.0k
}
744
745
static bool
746
json_insert_str(struct json_item_list *items, char *name, char **strp,
747
    unsigned int lineno)
748
7.78k
{
749
7.78k
    struct json_item *item;
750
7.78k
    debug_decl(json_insert_str, SUDO_DEBUG_UTIL);
751
752
7.78k
    if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL)
753
0
  debug_return_bool(false);
754
7.78k
    item->u.string = json_parse_string(strp);
755
7.78k
    if (item->u.string == NULL) {
756
28
  free(item);
757
28
  debug_return_bool(false);
758
28
    }
759
7.75k
    TAILQ_INSERT_TAIL(items, item, entries);
760
761
7.75k
    debug_return_bool(true);
762
7.75k
}
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.6k
{
769
14.6k
    struct json_item *item;
770
14.6k
    debug_decl(json_stack_push, SUDO_DEBUG_UTIL);
771
772
    /* We limit the stack size rather than expanding it. */
773
14.6k
    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.6k
    if ((item = new_json_item(type, name, lineno)) == NULL)
780
0
  debug_return_ptr(NULL);
781
14.6k
    TAILQ_INIT(&item->u.child.items);
782
14.6k
    item->u.child.parent = item;
783
14.6k
    TAILQ_INSERT_TAIL(items, item, entries);
784
785
    /* Push the current frame onto the stack (depth check performed above). */
786
14.6k
    stack->frames[stack->depth++] = frame;
787
788
    /* Return the new frame */
789
14.6k
    debug_return_ptr(&item->u.child);
790
14.6k
}
791
792
/* Only expect a value if a name is defined or we are in an array. */
793
38.8k
#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.20k
{
798
3.20k
    struct eventlog_json_object *frame, *root;
799
3.20k
    struct json_stack stack = JSON_STACK_INTIALIZER(stack);
800
3.20k
    unsigned int lineno = 0;
801
3.20k
    char *name = NULL;
802
3.20k
    char *cp, *line = NULL;
803
3.20k
    size_t len, linesize = 0;
804
3.20k
    ssize_t linelen;
805
3.20k
    bool saw_comma = false;
806
3.20k
    long long num;
807
3.20k
    char ch;
808
3.20k
    debug_decl(eventlog_json_read, SUDO_DEBUG_UTIL);
809
810
3.20k
    root = malloc(sizeof(*root));
811
3.20k
    if (root == NULL)
812
0
  goto bad;
813
814
3.20k
    root->parent = NULL;
815
3.20k
    TAILQ_INIT(&root->items);
816
817
3.20k
    frame = root;
818
6.75k
    while ((linelen = getdelim(&line, &linesize, '\n', fp)) != -1) {
819
4.25k
  char *ep = line + linelen - 1;
820
4.25k
  cp = line;
821
822
4.25k
  lineno++;
823
824
  /* Trim trailing whitespace. */
825
5.11k
  while (ep > cp && isspace((unsigned char)*ep))
826
868
      ep--;
827
4.25k
  ep[1] = '\0';
828
829
67.2k
  for (;;) {
830
67.2k
      const char *errstr;
831
832
      /* Trim leading whitespace, skip blank lines. */
833
67.2k
      while (isspace((unsigned char)*cp))
834
1.50k
    cp++;
835
836
      /* Check for comma separator and strip it out. */
837
67.2k
      if (*cp == ',') {
838
25.0k
    saw_comma = true;
839
25.0k
    cp++;
840
25.0k
    while (isspace((unsigned char)*cp))
841
233
        cp++;
842
25.0k
      }
843
844
      /* End of line? */
845
67.2k
      if (*cp == '\0')
846
3.55k
    break;
847
848
63.7k
      switch (*cp) {
849
6.33k
      case '{':
850
6.33k
    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.32k
    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.32k
    cp++;
861
6.32k
    saw_comma = false;
862
6.32k
    frame = json_stack_push(&stack, &frame->items, frame,
863
6.32k
        JSON_OBJECT, name, lineno);
864
6.32k
    if (frame == NULL)
865
3
        goto bad;
866
6.31k
    name = NULL;
867
6.31k
    break;
868
4.66k
      case '}':
869
4.66k
    if (stack.depth == 0 || frame->parent == NULL ||
870
4.65k
      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.65k
    cp++;
876
4.65k
    frame = stack.frames[--stack.depth];
877
4.65k
    saw_comma = false;
878
4.65k
    break;
879
8.33k
      case '[':
880
8.33k
    if (frame->parent == NULL) {
881
        /* Must have an enclosing object. */
882
2
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
883
2
      U_("unexpected array"));
884
2
        goto bad;
885
2
    }
886
8.33k
    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.33k
    cp++;
892
8.33k
    saw_comma = false;
893
8.33k
    frame = json_stack_push(&stack, &frame->items, frame,
894
8.33k
        JSON_ARRAY, name, lineno);
895
8.33k
    if (frame == NULL)
896
4
        goto bad;
897
8.33k
    name = NULL;
898
8.33k
    break;
899
5.44k
      case ']':
900
5.44k
    if (stack.depth == 0 || frame->parent == NULL ||
901
5.44k
      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.44k
    cp++;
907
5.44k
    frame = stack.frames[--stack.depth];
908
5.44k
    saw_comma = false;
909
5.44k
    break;
910
24.5k
      case '"':
911
24.5k
    if (frame->parent == NULL) {
912
        /* Must have an enclosing object. */
913
3
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
914
3
      U_("unexpected string"));
915
3
        goto bad;
916
3
    }
917
918
24.5k
    if (!expect_value) {
919
        /* Parse "name": */
920
16.7k
        if ((name = json_parse_string(&cp)) == NULL)
921
57
      goto bad;
922
        /* TODO: allow colon on next line? */
923
16.6k
        if (*cp != ':') {
924
185
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
925
185
          U_("missing colon after name"));
926
185
      goto bad;
927
185
        }
928
16.4k
        cp++;
929
16.4k
    } else {
930
7.79k
        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.78k
        saw_comma = false;
936
7.78k
        if (!json_insert_str(&frame->items, name, &cp, lineno))
937
28
      goto bad;
938
7.75k
        name = NULL;
939
7.75k
    }
940
24.2k
    break;
941
24.2k
      case 't':
942
1.46k
    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.43k
    cp += sizeof("true") - 1;
950
1.43k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
951
11
        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.29k
      case 'f':
964
1.29k
    if (strncmp(cp, "false", sizeof("false") - 1) != 0)
965
38
        goto parse_error;
966
1.25k
    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.25k
    cp += sizeof("false") - 1;
972
1.25k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
973
11
        goto parse_error;
974
1.24k
    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.22k
    saw_comma = false;
980
981
1.22k
    if (!json_insert_bool(&frame->items, name, false, lineno))
982
0
        goto bad;
983
1.22k
    name = NULL;
984
1.22k
    break;
985
1.39k
      case 'n':
986
1.39k
    if (strncmp(cp, "null", sizeof("null") - 1) != 0)
987
27
        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.36k
    cp += sizeof("null") - 1;
994
1.36k
    if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
995
11
        goto parse_error;
996
1.35k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
997
17
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
998
17
      U_("missing separator between values"));
999
17
        goto bad;
1000
17
    }
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
6.58k
      case '+': case '-': case '0': case '1': case '2': case '3':
1008
10.2k
      case '4': case '5': case '6': case '7': case '8': case '9':
1009
10.2k
    if (!expect_value) {
1010
18
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
1011
18
      U_("unexpected number"));
1012
18
        goto bad;
1013
18
    }
1014
    /* XXX - strtonumx() would be simpler here. */
1015
10.2k
    len = strcspn(cp, " \f\n\r\t\v,");
1016
10.2k
    ch = cp[len];
1017
10.2k
    cp[len] = '\0';
1018
10.2k
    if (!saw_comma && !TAILQ_EMPTY(&frame->items)) {
1019
6
        sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line, 
1020
6
      U_("missing separator between values"));
1021
6
        goto bad;
1022
6
    }
1023
10.2k
    saw_comma = false;
1024
10.2k
    num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
1025
10.2k
    if (errstr != NULL) {
1026
157
        sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - line,
1027
157
      cp, U_(errstr));
1028
157
        goto bad;
1029
157
    }
1030
10.0k
    cp += len;
1031
10.0k
    *cp = ch;
1032
1033
10.0k
    if (!json_insert_num(&frame->items, name, num, lineno))
1034
0
        goto bad;
1035
10.0k
    name = NULL;
1036
10.0k
    break;
1037
18
      default:
1038
18
    goto parse_error;
1039
63.7k
      }
1040
63.7k
  }
1041
4.25k
    }
1042
2.50k
    if (stack.depth != 0) {
1043
619
  frame = stack.frames[stack.depth - 1];
1044
619
  if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) {
1045
545
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1046
545
    U_("unmatched close brace"));
1047
545
  } else {
1048
74
      sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - line,
1049
74
    U_("unmatched close bracket"));
1050
74
  }
1051
619
  goto bad;
1052
619
    }
1053
1054
1.88k
    goto done;
1055
1056
1.88k
parse_error:
1057
145
    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.20k
done:
1062
3.20k
    free(line);
1063
3.20k
    free(name);
1064
1065
3.20k
    debug_return_ptr(root);
1066
3.20k
}