/* * SPDX-License-Identifier: ISC * * Copyright (c) 2020-2021 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This is an open source non-commercial project. Dear PVS-Studio, please check it. * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com */ #include #include #include #ifdef HAVE_STDBOOL_H # include #else # include "compat/stdbool.h" #endif /* HAVE_STDBOOL_H */ #include #include #include #include #include #include #include "sudo_compat.h" #include "sudo_debug.h" #include "sudo_eventlog.h" #include "sudo_fatal.h" #include "sudo_gettext.h" #include "sudo_iolog.h" #include "sudo_util.h" #include "iolog_json.h" struct json_stack { unsigned int depth; unsigned int maxdepth; struct json_object *frames[64]; }; #define JSON_STACK_INTIALIZER(s) { 0, nitems((s).frames) }; static bool json_store_columns(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_columns, SUDO_DEBUG_UTIL); if (item->u.number < 1 || item->u.number > INT_MAX) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "tty cols %lld: out of range", item->u.number); evlog->columns = 0; debug_return_bool(false); } evlog->columns = item->u.number; debug_return_bool(true); } static bool json_store_command(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_command, SUDO_DEBUG_UTIL); /* * Note: struct eventlog must store command + args. * We don't have argv yet so we append the args later. */ free(evlog->command); evlog->command = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_dumped_core(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_dumped_core, SUDO_DEBUG_UTIL); evlog->dumped_core = item->u.boolean; debug_return_bool(true); } static bool json_store_exit_value(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_exit_value, SUDO_DEBUG_UTIL); if (item->u.number < 0 || item->u.number > INT_MAX) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "exit value %lld: out of range", item->u.number); evlog->exit_value = -1; debug_return_bool(false); } evlog->exit_value = item->u.number; debug_return_bool(true); } static bool json_store_lines(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_lines, SUDO_DEBUG_UTIL); if (item->u.number < 1 || item->u.number > INT_MAX) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "tty lines %lld: out of range", item->u.number); evlog->lines = 0; debug_return_bool(false); } evlog->lines = item->u.number; debug_return_bool(true); } char ** json_array_to_strvec(struct json_object *array) { struct json_item *item; int len = 0; char **ret; debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL); TAILQ_FOREACH(item, &array->items, entries) { /* Can only convert arrays of string. */ if (item->type != JSON_STRING) { sudo_warnx(U_("expected JSON_STRING, got %d"), item->type); debug_return_ptr(NULL); } /* Prevent integer overflow. */ if (++len == INT_MAX) { sudo_warnx("%s", U_("JSON_ARRAY too large")); debug_return_ptr(NULL); } } if ((ret = reallocarray(NULL, len + 1, sizeof(char *))) == NULL) { sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); debug_return_ptr(NULL); } len = 0; TAILQ_FOREACH(item, &array->items, entries) { ret[len++] = item->u.string; item->u.string = NULL; } ret[len] = NULL; debug_return_ptr(ret); } static bool json_store_runargv(struct json_item *item, struct eventlog *evlog) { int i; debug_decl(json_store_runargv, SUDO_DEBUG_UTIL); if (evlog->argv != NULL) { for (i = 0; evlog->argv[i] != NULL; i++) free(evlog->argv[i]); free(evlog->argv); } evlog->argv = json_array_to_strvec(&item->u.child); debug_return_bool(evlog->argv != NULL); } static bool json_store_runenv(struct json_item *item, struct eventlog *evlog) { int i; debug_decl(json_store_runenv, SUDO_DEBUG_UTIL); if (evlog->envp != NULL) { for (i = 0; evlog->envp[i] != NULL; i++) free(evlog->envp[i]); free(evlog->envp); } evlog->envp = json_array_to_strvec(&item->u.child); debug_return_bool(evlog->envp != NULL); } static bool json_store_rungid(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_rungid, SUDO_DEBUG_UTIL); evlog->rungid = (gid_t)item->u.number; debug_return_bool(true); } static bool json_store_rungroup(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL); free(evlog->rungroup); evlog->rungroup = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_runuid(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_runuid, SUDO_DEBUG_UTIL); evlog->runuid = (uid_t)item->u.number; debug_return_bool(true); } static bool json_store_runuser(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_runuser, SUDO_DEBUG_UTIL); free(evlog->runuser); evlog->runuser = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_runchroot(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_runchroot, SUDO_DEBUG_UTIL); free(evlog->runchroot); evlog->runchroot = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_runcwd(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_runcwd, SUDO_DEBUG_UTIL); free(evlog->runcwd); evlog->runcwd = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_signal(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_signal, SUDO_DEBUG_UTIL); free(evlog->signal_name); evlog->signal_name = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_submitcwd(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL); free(evlog->cwd); evlog->cwd = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_submithost(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_submithost, SUDO_DEBUG_UTIL); free(evlog->submithost); evlog->submithost = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_submituser(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_submituser, SUDO_DEBUG_UTIL); free(evlog->submituser); evlog->submituser = item->u.string; item->u.string = NULL; debug_return_bool(true); } static bool json_store_timespec(struct json_item *item, struct timespec *ts) { struct json_object *object; debug_decl(json_store_timespec, SUDO_DEBUG_UTIL); object = &item->u.child; TAILQ_FOREACH(item, &object->items, entries) { if (item->type != JSON_NUMBER) continue; if (strcmp(item->name, "seconds") == 0) { ts->tv_sec = item->u.number; continue; } if (strcmp(item->name, "nanoseconds") == 0) { ts->tv_nsec = item->u.number; continue; } } debug_return_bool(true); } static bool json_store_run_time(struct json_item *item, struct eventlog *evlog) { return json_store_timespec(item, &evlog->run_time); } static bool json_store_timestamp(struct json_item *item, struct eventlog *evlog) { return json_store_timespec(item, &evlog->submit_time); } static bool json_store_ttyname(struct json_item *item, struct eventlog *evlog) { debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL); free(evlog->ttyname); evlog->ttyname = item->u.string; item->u.string = NULL; debug_return_bool(true); } static struct iolog_json_key { const char *name; enum json_value_type type; bool (*setter)(struct json_item *, struct eventlog *); } iolog_json_keys[] = { { "columns", JSON_NUMBER, json_store_columns }, { "command", JSON_STRING, json_store_command }, { "dumped_core", JSON_BOOL, json_store_dumped_core }, { "exit_value", JSON_NUMBER, json_store_exit_value }, { "lines", JSON_NUMBER, json_store_lines }, { "run_time", JSON_OBJECT, json_store_run_time }, { "runargv", JSON_ARRAY, json_store_runargv }, { "runenv", JSON_ARRAY, json_store_runenv }, { "rungid", JSON_ID, json_store_rungid }, { "rungroup", JSON_STRING, json_store_rungroup }, { "runuid", JSON_ID, json_store_runuid }, { "runuser", JSON_STRING, json_store_runuser }, { "runchroot", JSON_STRING, json_store_runchroot }, { "runcwd", JSON_STRING, json_store_runcwd }, { "signal", JSON_STRING, json_store_signal }, { "submitcwd", JSON_STRING, json_store_submitcwd }, { "submithost", JSON_STRING, json_store_submithost }, { "submituser", JSON_STRING, json_store_submituser }, { "timestamp", JSON_OBJECT, json_store_timestamp }, { "ttyname", JSON_STRING, json_store_ttyname }, { NULL } }; static struct json_item * new_json_item(enum json_value_type type, char *name, unsigned int lineno) { struct json_item *item; debug_decl(new_json_item, SUDO_DEBUG_UTIL); if ((item = malloc(sizeof(*item))) == NULL) { sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); debug_return_ptr(NULL); } item->name = name; item->type = type; item->lineno = lineno; debug_return_ptr(item); } static char * json_parse_string(char **strp) { char *dst, *end, *ret, *src = *strp + 1; size_t len; debug_decl(json_parse_string, SUDO_DEBUG_UTIL); for (end = src; *end != '"' && *end != '\0'; end++) { if (end[0] == '\\' && end[1] == '"') end++; } if (*end != '"') { sudo_warnx("%s", U_("missing double quote in name")); debug_return_str(NULL); } len = (size_t)(end - src); /* Copy string, flattening escaped chars. */ dst = ret = malloc(len + 1); if (dst == NULL) { sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); debug_return_str(NULL); } while (src < end) { char ch = *src++; if (ch == '\\') { switch (*src) { case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'u': /* Only currently handles 8-bit ASCII. */ if (src[1] == '0' && src[2] == '0') { ch = sudo_hexchar(&src[3]); if (ch != -1) { src += 4; break; } } /* Not in \u00XX format. */ FALLTHROUGH; case '"': case '\\': default: /* Note: a bare \ at the end of a string will be removed. */ ch = *src; break; } src++; } *dst++ = ch; } *dst = '\0'; /* Trim trailing whitespace. */ do { end++; } while (isspace((unsigned char)*end)); *strp = end; debug_return_str(ret); } void free_json_items(struct json_item_list *items) { struct json_item *item; debug_decl(free_json_items, SUDO_DEBUG_UTIL); while ((item = TAILQ_FIRST(items)) != NULL) { TAILQ_REMOVE(items, item, entries); switch (item->type) { case JSON_STRING: free(item->u.string); break; case JSON_ARRAY: case JSON_OBJECT: free_json_items(&item->u.child.items); break; case JSON_ID: case JSON_NUMBER: case JSON_BOOL: case JSON_NULL: /* Nothing to free. */ break; default: sudo_warnx("%s: internal error, invalid JSON type %d", __func__, item->type); break; } free(item->name); free(item); } debug_return; } static bool iolog_parse_json_object(struct json_object *object, struct eventlog *evlog) { struct json_item *item; bool ret = false; debug_decl(iolog_parse_json_object, SUDO_DEBUG_UTIL); /* First object holds all the actual data. */ item = TAILQ_FIRST(&object->items); if (item == NULL) { sudo_warnx("%s", U_("missing JSON_OBJECT")); goto done; } if (item->type != JSON_OBJECT) { sudo_warnx(U_("expected JSON_OBJECT, got %d"), item->type); goto done; } object = &item->u.child; TAILQ_FOREACH(item, &object->items, entries) { struct iolog_json_key *key; /* expecting key:value pairs */ if (item->name == NULL) { sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, "%s: missing object name", __func__); goto done; } /* lookup name */ for (key = iolog_json_keys; key->name != NULL; key++) { if (strcmp(item->name, key->name) == 0) break; } if (key->name == NULL) { sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, "%s: unknown key %s", __func__, item->name); } else if (key->type != item->type && (key->type != JSON_ID || item->type != JSON_NUMBER)) { sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, "%s: key mismatch %s type %d, expected %d", __func__, item->name, item->type, key->type); goto done; } else { /* Matched name and type. */ if (!key->setter(item, evlog)) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "unable to store %s", key->name); goto done; } } } ret = true; done: debug_return_bool(ret); } static bool json_insert_bool(struct json_item_list *items, char *name, bool value, unsigned int lineno) { struct json_item *item; debug_decl(json_insert_bool, SUDO_DEBUG_UTIL); if ((item = new_json_item(JSON_BOOL, name, lineno)) == NULL) debug_return_bool(false); item->u.boolean = value; TAILQ_INSERT_TAIL(items, item, entries); debug_return_bool(true); } static bool json_insert_null(struct json_item_list *items, char *name, unsigned int lineno) { struct json_item *item; debug_decl(json_insert_null, SUDO_DEBUG_UTIL); if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL) debug_return_bool(false); TAILQ_INSERT_TAIL(items, item, entries); debug_return_bool(true); } static bool json_insert_num(struct json_item_list *items, char *name, long long value, unsigned int lineno) { struct json_item *item; debug_decl(json_insert_num, SUDO_DEBUG_UTIL); if ((item = new_json_item(JSON_NUMBER, name, lineno)) == NULL) debug_return_bool(false); item->u.number = value; TAILQ_INSERT_TAIL(items, item, entries); debug_return_bool(true); } static bool json_insert_str(struct json_item_list *items, char *name, char **strp, unsigned int lineno) { struct json_item *item; debug_decl(json_insert_str, SUDO_DEBUG_UTIL); if ((item = new_json_item(JSON_STRING, name, lineno)) == NULL) debug_return_bool(false); item->u.string = json_parse_string(strp); if (item->u.string == NULL) { free(item); debug_return_bool(false); } TAILQ_INSERT_TAIL(items, item, entries); debug_return_bool(true); } static struct json_object * json_stack_push(struct json_stack *stack, struct json_item_list *items, struct json_object *frame, enum json_value_type type, char *name, unsigned int lineno) { struct json_item *item; debug_decl(json_stack_push, SUDO_DEBUG_UTIL); /* We limit the stack size rather than expanding it. */ if (stack->depth >= stack->maxdepth) { sudo_warnx(U_("json stack exhausted (max %u frames)"), stack->maxdepth); debug_return_ptr(NULL); } /* Allocate a new item and insert it into the list. */ if ((item = new_json_item(type, name, lineno)) == NULL) debug_return_ptr(NULL); TAILQ_INIT(&item->u.child.items); item->u.child.parent = item; TAILQ_INSERT_TAIL(items, item, entries); /* Push the current frame onto the stack (depth check performed above). */ stack->frames[stack->depth++] = frame; /* Return the new frame */ debug_return_ptr(&item->u.child); } /* Only expect a value if a name is defined or we are in an array. */ #define expect_value (name != NULL || (frame->parent != NULL && frame->parent->type == JSON_ARRAY)) bool iolog_parse_json(FILE *fp, const char *filename, struct json_object *root) { struct json_object *frame = root; struct json_stack stack = JSON_STACK_INTIALIZER(stack); unsigned int lineno = 0; char *name = NULL; char *cp, *buf = NULL; size_t bufsize = 0; ssize_t len; bool ret = false; bool saw_comma = false; long long num; char ch; debug_decl(iolog_parse_json, SUDO_DEBUG_UTIL); root->parent = NULL; TAILQ_INIT(&root->items); while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) { char *ep = buf + len - 1; cp = buf; lineno++; /* Trim trailing whitespace. */ while (ep > cp && isspace((unsigned char)*ep)) ep--; ep[1] = '\0'; for (;;) { const char *errstr; /* Trim leading whitespace, skip blank lines. */ while (isspace((unsigned char)*cp)) cp++; /* Check for comma separator and strip it out. */ if (*cp == ',') { saw_comma = true; cp++; while (isspace((unsigned char)*cp)) cp++; } /* End of line? */ if (*cp == '\0') break; switch (*cp) { case '{': if (name == NULL && frame->parent != NULL) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("objects must consist of name:value pairs")); goto done; } if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } cp++; saw_comma = false; frame = json_stack_push(&stack, &frame->items, frame, JSON_OBJECT, name, lineno); if (frame == NULL) goto done; name = NULL; break; case '}': if (stack.depth == 0 || frame->parent == NULL || frame->parent->type != JSON_OBJECT) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unmatched close brace")); goto done; } cp++; frame = stack.frames[--stack.depth]; saw_comma = false; break; case '[': if (frame->parent == NULL) { /* Must have an enclosing object. */ sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected array")); goto done; } if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } cp++; saw_comma = false; frame = json_stack_push(&stack, &frame->items, frame, JSON_ARRAY, name, lineno); if (frame == NULL) goto done; name = NULL; break; case ']': if (stack.depth == 0 || frame->parent == NULL || frame->parent->type != JSON_ARRAY) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unmatched close bracket")); goto done; } cp++; frame = stack.frames[--stack.depth]; saw_comma = false; break; case '"': if (frame->parent == NULL) { /* Must have an enclosing object. */ sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected string")); goto done; } if (!expect_value) { /* Parse "name": */ if ((name = json_parse_string(&cp)) == NULL) goto done; /* TODO: allow colon on next line? */ if (*cp != ':') { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing colon after name")); goto done; } cp++; } else { if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } saw_comma = false; if (!json_insert_str(&frame->items, name, &cp, lineno)) goto done; name = NULL; } break; case 't': if (strncmp(cp, "true", sizeof("true") - 1) != 0) goto parse_error; if (!expect_value) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected boolean")); goto done; } cp += sizeof("true") - 1; if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0') goto parse_error; if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } saw_comma = false; if (!json_insert_bool(&frame->items, name, true, lineno)) goto done; name = NULL; break; case 'f': if (strncmp(cp, "false", sizeof("false") - 1) != 0) goto parse_error; if (!expect_value) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected boolean")); goto done; } cp += sizeof("false") - 1; if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0') goto parse_error; if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } saw_comma = false; if (!json_insert_bool(&frame->items, name, false, lineno)) goto done; name = NULL; break; case 'n': if (strncmp(cp, "null", sizeof("null") - 1) != 0) goto parse_error; if (!expect_value) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected null")); goto done; } cp += sizeof("null") - 1; if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0') goto parse_error; if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } saw_comma = false; if (!json_insert_null(&frame->items, name, lineno)) goto done; name = NULL; break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (!expect_value) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unexpected number")); goto done; } /* XXX - strtonumx() would be simpler here. */ len = strcspn(cp, " \f\n\r\t\v,"); ch = cp[len]; cp[len] = '\0'; if (!saw_comma && !TAILQ_EMPTY(&frame->items)) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("missing separator between values")); goto done; } saw_comma = false; num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr); if (errstr != NULL) { sudo_warnx("%s:%u:%td: %s: %s", filename, lineno, cp - buf, cp, U_(errstr)); goto done; } cp += len; *cp = ch; if (!json_insert_num(&frame->items, name, num, lineno)) goto done; name = NULL; break; default: goto parse_error; } } } if (stack.depth != 0) { frame = stack.frames[stack.depth - 1]; if (frame->parent == NULL || frame->parent->type == JSON_OBJECT) { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unmatched close brace")); } else { sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("unmatched close bracket")); } goto done; } ret = true; goto done; parse_error: sudo_warnx("%s:%u:%td: %s", filename, lineno, cp - buf, U_("parse error")); done: free(buf); free(name); if (!ret) free_json_items(&root->items); debug_return_bool(ret); } bool iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct eventlog *evlog) { struct json_object root; bool ret = false; debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL); if (iolog_parse_json(fp, iolog_dir, &root)) { /* Walk the stack and parse entries. */ ret = iolog_parse_json_object(&root, evlog); /* Cleanup. */ free_json_items(&root.items); } debug_return_bool(ret); }