Coverage Report

Created: 2025-07-11 06:58

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