/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  | }  |