2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-22 01:49:11 +00:00

Parse I/O JSON info file in JSON if present.

The JSON version includes more information than the original "log"
file in the I/O log dir.
This commit is contained in:
Todd C. Miller 2020-03-29 05:05:08 -06:00
parent ea9b711a70
commit 056173e572
8 changed files with 891 additions and 55 deletions

View File

@ -99,6 +99,7 @@ install-sh
lib/iolog/Makefile.in
lib/iolog/hostcheck.c
lib/iolog/iolog_fileio.c
lib/iolog/iolog_json.c
lib/iolog/iolog_path.c
lib/iolog/iolog_util.c
lib/iolog/regress/iolog_path/check_iolog_path.c

View File

@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: ISC
.\"
.\" Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws>
.\" Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
@ -16,7 +16,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.TH "SUDOREPLAY" "@mansectsu@" "August 27, 2019" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.TH "SUDOREPLAY" "@mansectsu@" "March 26, 2019" "Sudo @PACKAGE_VERSION@" "System Manager's Manual"
.nh
.if n .ad l
.SH "NAME"
@ -183,6 +183,10 @@ was explicitly specified when
\fBsudo\fR
was run this field will be empty in the log.
.TP 8n
host \fIhostname\fR
Evaluates to true if the command was run on the specified
\fIhostname\fR.
.TP 8n
runas \fIrunas_user\fR
Evaluates to true if the command was run as the specified
\fIrunas_user\fR.
@ -396,6 +400,9 @@ The default I/O log directory.
\fI@iolog_dir@/00/00/01/log\fR
Example session log info.
.TP 26n
\fI@iolog_dir@/00/00/01/log.json\fR
Example session log info (JSON format).
.TP 26n
\fI@iolog_dir@/00/00/01/stdin\fR
Example session standard input log.
.TP 26n

View File

@ -1,7 +1,7 @@
.\"
.\" SPDX-License-Identifier: ISC
.\"
.\" Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws>
.\" Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd August 27, 2019
.Dd March 26, 2019
.Dt SUDOREPLAY @mansectsu@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@ -168,6 +168,9 @@ Note that unless a
was explicitly specified when
.Nm sudo
was run this field will be empty in the log.
.It host Ar hostname
Evaluates to true if the command was run on the specified
.Ar hostname .
.It runas Ar runas_user
Evaluates to true if the command was run as the specified
.Ar runas_user .
@ -358,6 +361,8 @@ Debugging framework configuration
The default I/O log directory.
.It Pa @iolog_dir@/00/00/01/log
Example session log info.
.It Pa @iolog_dir@/00/00/01/log.json
Example session log info (JSON format).
.It Pa @iolog_dir@/00/00/01/stdin
Example session standard input log.
.It Pa @iolog_dir@/00/00/01/stdout

View File

@ -115,6 +115,7 @@ bool iolog_parse_timing(const char *line, struct timing_closure *timing);
char *iolog_parse_delay(const char *cp, struct timespec *delay, const char *decimal_point);
int iolog_read_timing_record(struct iolog_file *iol, struct timing_closure *timing);
struct iolog_info *iolog_parse_loginfo(int dfd, const char *iolog_dir);
bool iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li);
void iolog_adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor);
void iolog_free_loginfo(struct iolog_info *li);

View File

@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2011-2019 Todd C. Miller <Todd.Miller@sudo.ws>
# Copyright (c) 2011-2020 Todd C. Miller <Todd.Miller@sudo.ws>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@ -86,7 +86,8 @@ DEVEL = @DEVEL@
SHELL = @SHELL@
LIBIOLOG_OBJS = iolog_fileio.lo iolog_path.lo iolog_util.lo hostcheck.lo
LIBIOLOG_OBJS = iolog_fileio.lo iolog_json.lo iolog_path.lo iolog_util.lo \
hostcheck.lo
IOBJS = $(LIBIOLOG_OBJS:.lo=.i)
@ -94,7 +95,7 @@ POBJS = $(IOBJS:.i=.plog)
CHECK_IOLOG_PATH_OBJS = check_iolog_path.lo iolog_path.lo
CHECK_IOLOG_UTIL_OBJS = check_iolog_util.lo iolog_util.lo
CHECK_IOLOG_UTIL_OBJS = check_iolog_util.lo iolog_json.lo iolog_util.lo
all: libsudo_iolog.la
@ -235,6 +236,22 @@ iolog_fileio.i: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_fileio.plog: iolog_fileio.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_fileio.c --i-file $< --output-file $@
iolog_json.lo: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_json.c
iolog_json.i: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_json.plog: iolog_json.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_json.c --i-file $< --output-file $@
iolog_path.lo: $(srcdir)/iolog_path.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \

760
lib/iolog/iolog_json.c Normal file
View File

@ -0,0 +1,760 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
*
* 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 <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#include <time.h>
#include "sudo_gettext.h" /* must be included before sudo_compat.h */
#include "sudo_compat.h"
#include "sudo_fatal.h"
#include "sudo_debug.h"
#include "sudo_queue.h"
#include "sudo_json.h"
#include "sudo_util.h"
#include "sudo_iolog.h"
TAILQ_HEAD(json_item_list, json_item);
struct json_object {
struct json_item *parent;
struct json_item_list items;
};
struct json_item {
TAILQ_ENTRY(json_item) entries;
char *name; /* may be NULL for first brace */
unsigned int lineno;
enum json_value_type type;
union {
struct json_object child;
char *string;
long long number;
id_t id;
bool boolean;
} u;
};
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 iolog_info *li)
{
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);
li->cols = 0;
debug_return_bool(false);
}
li->cols = item->u.number;
debug_return_bool(true);
}
static bool
json_store_command(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_command, SUDO_DEBUG_UTIL);
/*
* Note: struct iolog_info must store command + args.
* We don't have argv yet so we append the args later.
*/
li->cmd = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_lines(struct json_item *item, struct iolog_info *li)
{
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);
li->lines = 0;
debug_return_bool(false);
}
li->lines = item->u.number;
debug_return_bool(true);
}
static 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) {
if (item->type != JSON_STRING) {
sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
debug_return_ptr(NULL);
}
len++;
}
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 iolog_info *li)
{
debug_decl(json_store_runargv, SUDO_DEBUG_UTIL);
li->argv = json_array_to_strvec(&item->u.child);
debug_return_bool(li->argv != NULL);
}
static bool
json_store_runenv(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_runenv, SUDO_DEBUG_UTIL);
li->envp = json_array_to_strvec(&item->u.child);
debug_return_bool(li->envp != NULL);
}
static bool
json_store_rungid(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_rungid, SUDO_DEBUG_UTIL);
li->runas_gid = (gid_t)item->u.number;
debug_return_bool(true);
}
static bool
json_store_rungroup(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_rungroup, SUDO_DEBUG_UTIL);
li->runas_group = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_runuid(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_runuid, SUDO_DEBUG_UTIL);
li->runas_uid = (uid_t)item->u.number;
debug_return_bool(true);
}
static bool
json_store_runuser(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_runuser, SUDO_DEBUG_UTIL);
li->runas_user = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_submitcwd(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_submitcwd, SUDO_DEBUG_UTIL);
li->cwd = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_submithost(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_submithost, SUDO_DEBUG_UTIL);
li->host = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_submituser(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_submituser, SUDO_DEBUG_UTIL);
li->user = item->u.string;
item->u.string = NULL;
debug_return_bool(true);
}
static bool
json_store_timestamp(struct json_item *item, struct iolog_info *li)
{
struct json_object *object;
debug_decl(json_store_timestamp, 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) {
li->tstamp.tv_sec = item->u.number;
continue;
}
if (strcmp(item->name, "nanoseconds") == 0) {
li->tstamp.tv_nsec = item->u.number;
continue;
}
}
debug_return_bool(true);
}
static bool
json_store_ttyname(struct json_item *item, struct iolog_info *li)
{
debug_decl(json_store_ttyname, SUDO_DEBUG_UTIL);
li->tty = 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 iolog_info *);
} iolog_json_keys[] = {
{ "columns", JSON_NUMBER, json_store_columns },
{ "command", JSON_STRING, json_store_command },
{ "lines", JSON_NUMBER, json_store_lines },
{ "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 },
{ "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(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 (ret == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
while (src < end) {
char ch = *src++;
/* TODO: handle unicode escapes */
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 '"':
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);
}
static 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;
default:
break;
}
free(item->name);
free(item);
}
debug_return;
}
static bool
iolog_parse_json_object(struct json_object *object, struct iolog_info *li)
{
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->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;
/* 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, li)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"unable to store %s", key->name);
goto done;
}
}
}
/* Merge cmd and argv as sudoreplay expects. */
if (li->cmd != NULL && li->argv != NULL) {
size_t len = strlen(li->cmd) + 1;
char *newcmd;
int ac;
/* Skip argv[0], we use li->cmd instead. */
for (ac = 1; li->argv[ac] != NULL; ac++)
len += strlen(li->argv[ac]) + 1;
if ((newcmd = malloc(len)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
/* TODO: optimize this. */
if (strlcpy(newcmd, li->cmd, len) >= len)
sudo_fatalx(U_("internal error, %s overflow"), __func__);
for (ac = 1; li->argv[ac] != NULL; ac++) {
if (strlcat(newcmd, " ", len) >= len)
sudo_fatalx(U_("internal error, %s overflow"), __func__);
if (strlcat(newcmd, li->argv[ac], len) >= len)
sudo_fatalx(U_("internal error, %s overflow"), __func__);
}
free(li->cmd);
li->cmd = newcmd;
}
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_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(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
/* 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. */
if (stack->depth == stack->maxdepth)
sudo_fatalx(U_("internal error, %s overflow"), __func__);
stack->frames[stack->depth++] = frame;
/* Return the new frame */
debug_return_ptr(&item->u.child);
}
bool
iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
{
struct json_object root = { NULL, TAILQ_HEAD_INITIALIZER(root.items) };
struct json_object *curobj = &root;
struct json_object *curarray = NULL;
struct json_stack objstack = JSON_STACK_INTIALIZER(objstack);
struct json_stack arrstack = JSON_STACK_INTIALIZER(arrstack);
unsigned int lineno = 0;
char *name = NULL;
char *buf = NULL;
size_t bufsize = 0;
ssize_t len;
long long num;
bool ret = false;
debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) {
char *cp = buf;
char *ep = buf + len - 1;
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++;
/* Strip out commas. TODO: require commas between values. */
if (*cp == ',') {
cp++;
while (isspace((unsigned char)*cp))
cp++;
}
if (*cp == '\0')
break;
switch (*cp) {
case '{':
cp++;
curobj = json_stack_push(&objstack, &curobj->items, curobj,
JSON_OBJECT, name, lineno);
if (curobj == NULL)
goto parse_error;
name = NULL;
break;
case '}':
cp++;
if (curobj->parent == NULL || curobj->parent->type != JSON_OBJECT) {
sudo_warnx(U_("unmatched close brace"));
goto parse_error;
}
curobj = objstack.frames[--objstack.depth];
break;
case '[':
cp++;
if (curobj->parent == NULL) {
/* Must have an enclosing object. */
sudo_warnx(U_("unexpected array"));
goto parse_error;
}
curarray = json_stack_push(&arrstack, &curobj->items, curarray,
JSON_ARRAY, name, lineno);
if (curarray == NULL)
goto parse_error;
name = NULL;
break;
case ']':
cp++;
if (curarray == NULL || curarray->parent == NULL ||
curarray->parent->type != JSON_ARRAY) {
sudo_warnx(U_("unmatched close bracket"));
goto parse_error;
}
curarray = arrstack.frames[--arrstack.depth];
break;
case '"':
if (curobj->parent == NULL) {
/* Must have an enclosing object. */
sudo_warnx(U_("unexpected string"));
goto parse_error;
}
if (curarray != NULL) {
if (!json_insert_str(&curarray->items, NULL, &cp, lineno))
goto parse_error;
} else if (name != NULL) {
if (!json_insert_str(&curobj->items, name, &cp, lineno))
goto parse_error;
name = NULL;
} else {
/* Parsing "name": */
if ((name = json_parse_string(&cp)) == NULL)
goto parse_error;
/* TODO: allow colon on next line? */
if (*cp++ != ':') {
sudo_warnx(U_("missing colon after name"));
goto parse_error;
}
}
break;
case 't':
if (name == NULL) {
sudo_warnx(U_("unexpected boolean"));
goto parse_error;
}
if (strcmp(cp, "true") != 0)
goto parse_error;
cp += sizeof("true") - 1;
if (curarray != NULL) {
if (!json_insert_bool(&curarray->items, NULL, true, lineno))
goto parse_error;
} else {
if (!json_insert_bool(&curobj->items, name, true, lineno))
goto parse_error;
name = NULL;
}
break;
case 'f':
if (name == NULL) {
sudo_warnx(U_("unexpected boolean"));
goto parse_error;
}
if (strcmp(cp, "false") != 0)
goto parse_error;
cp += sizeof("false") - 1;
if (curarray != NULL) {
if (!json_insert_bool(&curarray->items, NULL, false, lineno))
goto parse_error;
} else {
if (!json_insert_bool(&curobj->items, name, false, lineno))
goto parse_error;
name = NULL;
}
break;
case 'n':
if (strcmp(cp, "null") == 0)
sudo_warnx(U_("null not allowed"));
goto parse_error;
case '+': case '-': case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': case '8': case '9':
if (name == NULL) {
sudo_warnx(U_("unexpected number"));
goto parse_error;
}
len = strcspn(cp, " \t\r\n,");
cp[len] = '\0';
num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
if (errstr != NULL) {
sudo_warnx(U_("%s: %s"), cp, U_(errstr));
goto parse_error;
}
cp += len;
if (curarray != NULL) {
if (!json_insert_num(&curarray->items, NULL, num, lineno))
goto parse_error;
} else {
if (!json_insert_num(&curobj->items, name, num, lineno))
goto parse_error;
name = NULL;
}
break;
default:
goto parse_error;
}
}
}
if (objstack.depth != 0) {
sudo_warnx(U_("unmatched close brace"));
goto parse_error;
}
if (arrstack.depth != 0) {
sudo_warnx(U_("unmatched close bracket"));
goto parse_error;
}
/* Walk the stack and parse entries. */
ret = iolog_parse_json_object(&root, li);
goto done;
parse_error:
sudo_warnx(U_("%s/%s:%u unable to parse \"%s\""), iolog_dir,
"log.json", lineno, buf);
done:
free(buf);
free(name);
free_json_items(&root.items);
debug_return_bool(ret);
}

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2009-2019 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -31,11 +31,6 @@
#else
# include "compat/stdbool.h"
#endif /* HAVE_STDBOOL_H */
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
@ -60,32 +55,15 @@
static int timing_event_adj;
struct iolog_info *
iolog_parse_loginfo(int dfd, const char *iolog_dir)
static bool
iolog_parse_loginfo_legacy(FILE *fp, const char *iolog_dir,
struct iolog_info *li)
{
char *buf = NULL, *cp, *ep;
const char *errstr;
size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
struct iolog_info *li = NULL;
FILE *fp = NULL;
int fd = -1;
debug_decl(iolog_parse_loginfo, SUDO_DEBUG_UTIL);
if (dfd == -1) {
if ((dfd = open(iolog_dir, O_RDONLY)) == -1) {
sudo_warn("%s", iolog_dir);
goto bad;
}
fd = openat(dfd, "log", O_RDONLY, 0);
close(dfd);
} else {
fd = openat(dfd, "log", O_RDONLY, 0);
}
if (fd == -1 || (fp = fdopen(fd, "r")) == NULL) {
sudo_warn("%s/log", iolog_dir);
goto bad;
}
fd = -1;
bool ret = false;
debug_decl(iolog_parse_loginfo_legacy, SUDO_DEBUG_UTIL);
/*
* Info file has three lines:
@ -93,18 +71,12 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
* 2) cwd
* 3) command with args
*/
if ((li = calloc(1, sizeof(*li))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
li->runas_uid = (uid_t)-1;
li->runas_gid = (gid_t)-1;
if (getdelim(&buf, &bufsize, '\n', fp) == -1 ||
getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 ||
getdelim(&li->cmd, &cmdsize, '\n', fp) == -1) {
sudo_warn(U_("%s: invalid log file"), iolog_dir);
goto bad;
goto done;
}
fclose(fp);
fp = NULL;
/* Strip the newline from the cwd and command. */
li->cwd[strcspn(li->cwd, "\n")] = '\0';
@ -121,20 +93,20 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
/* timestamp */
if ((ep = strchr(cp, ':')) == NULL) {
sudo_warn(U_("%s: time stamp field is missing"), iolog_dir);
goto bad;
goto done;
}
*ep = '\0';
li->tstamp.tv_sec = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
if (errstr != NULL) {
sudo_warn(U_("%s: time stamp %s: %s"), iolog_dir, cp, errstr);
goto bad;
goto done;
}
/* submit user */
cp = ep + 1;
if ((ep = strchr(cp, ':')) == NULL) {
sudo_warn(U_("%s: user field is missing"), iolog_dir);
goto bad;
goto done;
}
if ((li->user = strndup(cp, (size_t)(ep - cp))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
@ -143,7 +115,7 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
cp = ep + 1;
if ((ep = strchr(cp, ':')) == NULL) {
sudo_warn(U_("%s: runas user field is missing"), iolog_dir);
goto bad;
goto done;
}
if ((li->runas_user = strndup(cp, (size_t)(ep - cp))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
@ -152,7 +124,7 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
cp = ep + 1;
if ((ep = strchr(cp, ':')) == NULL) {
sudo_warn(U_("%s: runas group field is missing"), iolog_dir);
goto bad;
goto done;
}
if (cp != ep) {
if ((li->runas_group = strndup(cp, (size_t)(ep - cp))) == NULL)
@ -189,15 +161,60 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
}
}
}
ret = true;
done:
free(buf);
debug_return_ptr(li);
debug_return_bool(ret);
}
struct iolog_info *
iolog_parse_loginfo(int dfd, const char *iolog_dir)
{
struct iolog_info *li = NULL;
FILE *fp = NULL;
int fd = -1;
int tmpfd = -1;
bool ok, legacy = false;
debug_decl(iolog_parse_loginfo, SUDO_DEBUG_UTIL);
if (dfd == -1) {
if ((tmpfd = open(iolog_dir, O_RDONLY)) == -1) {
sudo_warn("%s", iolog_dir);
goto bad;
}
dfd = tmpfd;
}
if ((fd = openat(dfd, "log.json", O_RDONLY, 0)) == -1) {
fd = openat(dfd, "log", O_RDONLY, 0);
legacy = true;
}
if (tmpfd != -1)
close(tmpfd);
if (fd == -1 || (fp = fdopen(fd, "r")) == NULL) {
sudo_warn("%s/log", iolog_dir);
goto bad;
}
fd = -1;
if ((li = calloc(1, sizeof(*li))) == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
li->runas_uid = (uid_t)-1;
li->runas_gid = (gid_t)-1;
ok = legacy ? iolog_parse_loginfo_legacy(fp, iolog_dir, li) :
iolog_parse_loginfo_json(fp, iolog_dir, li);
if (ok) {
fclose(fp);
debug_return_ptr(li);
}
bad:
if (fd != -1)
close(fd);
if (fp != NULL)
fclose(fp);
free(buf);
iolog_free_loginfo(li);
debug_return_ptr(NULL);
}
@ -422,7 +439,19 @@ iolog_read_timing_record(struct iolog_file *iol, struct timing_closure *timing)
void
iolog_free_loginfo(struct iolog_info *li)
{
char **p;
if (li != NULL) {
if (li->argv != NULL) {
for (p = li->argv; *p != NULL; p++)
free(*p);
free(li->argv);
}
if (li->envp != NULL) {
for (p = li->envp; *p != NULL; p++)
free(*p);
free(li->envp);
}
free(li->cwd);
free(li->user);
free(li->runas_user);

View File

@ -119,6 +119,7 @@ struct search_node {
#define ST_FROMDATE 7
#define ST_TODATE 8
#define ST_CWD 9
#define ST_HOST 10
char type;
bool negated;
bool or;
@ -126,6 +127,7 @@ struct search_node {
regex_t cmdre;
struct timespec tstamp;
char *cwd;
char *host;
char *tty;
char *user;
char *runas_group;
@ -199,8 +201,7 @@ static void setup_terminal(struct iolog_info *li, bool interactive, bool resize)
isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
(s)[5] == '/' && \
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
(s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \
(s)[12] == '\0')
(s)[8] == '\0')
__dso_public int main(int argc, char *argv[]);
@ -1171,6 +1172,11 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr)
goto bad;
type = ST_RUNASGROUP;
break;
case 'h': /* host */
if (strncmp(*av, "host", strlen(*av)) != 0)
goto bad;
type = ST_HOST;
break;
case 'r': /* runas user */
if (strncmp(*av, "runas", strlen(*av)) != 0)
goto bad;
@ -1259,20 +1265,28 @@ match_expr(struct search_node_list *head, struct iolog_info *log, bool last_matc
res = match_expr(&sn->u.expr, log, matched);
break;
case ST_CWD:
res = strcmp(sn->u.cwd, log->cwd) == 0;
if (log->cwd != NULL)
res = strcmp(sn->u.cwd, log->cwd) == 0;
break;
case ST_HOST:
if (log->host != NULL)
res = strcmp(sn->u.host, log->host) == 0;
break;
case ST_TTY:
res = strcmp(sn->u.tty, log->tty) == 0;
if (log->tty != NULL)
res = strcmp(sn->u.tty, log->tty) == 0;
break;
case ST_RUNASGROUP:
if (log->runas_group != NULL)
res = strcmp(sn->u.runas_group, log->runas_group) == 0;
break;
case ST_RUNASUSER:
res = strcmp(sn->u.runas_user, log->runas_user) == 0;
if (log->runas_user != NULL)
res = strcmp(sn->u.runas_user, log->runas_user) == 0;
break;
case ST_USER:
res = strcmp(sn->u.user, log->user) == 0;
if (log->user != NULL)
res = strcmp(sn->u.user, log->user) == 0;
break;
case ST_PATTERN:
rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0);
@ -1339,6 +1353,8 @@ list_session(char *log_dir, regex_t *re, const char *user, const char *tty)
li->user, li->tty, li->cwd, li->runas_user);
if (li->runas_group)
printf("GROUP=%s ; ", li->runas_group);
if (li->host)
printf("HOST=%s ; ", li->host);
printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
ret = 0;