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:
parent
ea9b711a70
commit
056173e572
1
MANIFEST
1
MANIFEST
@ -99,6 +99,7 @@ install-sh
|
|||||||
lib/iolog/Makefile.in
|
lib/iolog/Makefile.in
|
||||||
lib/iolog/hostcheck.c
|
lib/iolog/hostcheck.c
|
||||||
lib/iolog/iolog_fileio.c
|
lib/iolog/iolog_fileio.c
|
||||||
|
lib/iolog/iolog_json.c
|
||||||
lib/iolog/iolog_path.c
|
lib/iolog/iolog_path.c
|
||||||
lib/iolog/iolog_util.c
|
lib/iolog/iolog_util.c
|
||||||
lib/iolog/regress/iolog_path/check_iolog_path.c
|
lib/iolog/regress/iolog_path/check_iolog_path.c
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: ISC
|
.\" 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
|
.\" Permission to use, copy, modify, and distribute this software for any
|
||||||
.\" purpose with or without fee is hereby granted, provided that the above
|
.\" 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
|
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
.\" 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
|
.nh
|
||||||
.if n .ad l
|
.if n .ad l
|
||||||
.SH "NAME"
|
.SH "NAME"
|
||||||
@ -183,6 +183,10 @@ was explicitly specified when
|
|||||||
\fBsudo\fR
|
\fBsudo\fR
|
||||||
was run this field will be empty in the log.
|
was run this field will be empty in the log.
|
||||||
.TP 8n
|
.TP 8n
|
||||||
|
host \fIhostname\fR
|
||||||
|
Evaluates to true if the command was run on the specified
|
||||||
|
\fIhostname\fR.
|
||||||
|
.TP 8n
|
||||||
runas \fIrunas_user\fR
|
runas \fIrunas_user\fR
|
||||||
Evaluates to true if the command was run as the specified
|
Evaluates to true if the command was run as the specified
|
||||||
\fIrunas_user\fR.
|
\fIrunas_user\fR.
|
||||||
@ -396,6 +400,9 @@ The default I/O log directory.
|
|||||||
\fI@iolog_dir@/00/00/01/log\fR
|
\fI@iolog_dir@/00/00/01/log\fR
|
||||||
Example session log info.
|
Example session log info.
|
||||||
.TP 26n
|
.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
|
\fI@iolog_dir@/00/00/01/stdin\fR
|
||||||
Example session standard input log.
|
Example session standard input log.
|
||||||
.TP 26n
|
.TP 26n
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: ISC
|
.\" 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
|
.\" Permission to use, copy, modify, and distribute this software for any
|
||||||
.\" purpose with or without fee is hereby granted, provided that the above
|
.\" 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
|
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
.\"
|
.\"
|
||||||
.Dd August 27, 2019
|
.Dd March 26, 2019
|
||||||
.Dt SUDOREPLAY @mansectsu@
|
.Dt SUDOREPLAY @mansectsu@
|
||||||
.Os Sudo @PACKAGE_VERSION@
|
.Os Sudo @PACKAGE_VERSION@
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -168,6 +168,9 @@ Note that unless a
|
|||||||
was explicitly specified when
|
was explicitly specified when
|
||||||
.Nm sudo
|
.Nm sudo
|
||||||
was run this field will be empty in the log.
|
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
|
.It runas Ar runas_user
|
||||||
Evaluates to true if the command was run as the specified
|
Evaluates to true if the command was run as the specified
|
||||||
.Ar runas_user .
|
.Ar runas_user .
|
||||||
@ -358,6 +361,8 @@ Debugging framework configuration
|
|||||||
The default I/O log directory.
|
The default I/O log directory.
|
||||||
.It Pa @iolog_dir@/00/00/01/log
|
.It Pa @iolog_dir@/00/00/01/log
|
||||||
Example session log info.
|
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
|
.It Pa @iolog_dir@/00/00/01/stdin
|
||||||
Example session standard input log.
|
Example session standard input log.
|
||||||
.It Pa @iolog_dir@/00/00/01/stdout
|
.It Pa @iolog_dir@/00/00/01/stdout
|
||||||
|
@ -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);
|
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);
|
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);
|
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_adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor);
|
||||||
void iolog_free_loginfo(struct iolog_info *li);
|
void iolog_free_loginfo(struct iolog_info *li);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: ISC
|
# 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
|
# Permission to use, copy, modify, and distribute this software for any
|
||||||
# purpose with or without fee is hereby granted, provided that the above
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -86,7 +86,8 @@ DEVEL = @DEVEL@
|
|||||||
|
|
||||||
SHELL = @SHELL@
|
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)
|
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_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
|
all: libsudo_iolog.la
|
||||||
|
|
||||||
@ -235,6 +236,22 @@ iolog_fileio.i: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \
|
|||||||
$(CC) -E -o $@ $(CPPFLAGS) $<
|
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||||
iolog_fileio.plog: iolog_fileio.i
|
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 $@
|
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 \
|
iolog_path.lo: $(srcdir)/iolog_path.c $(incdir)/compat/stdbool.h \
|
||||||
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
|
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
|
||||||
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
|
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
|
||||||
|
760
lib/iolog/iolog_json.c
Normal file
760
lib/iolog/iolog_json.c
Normal 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);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: ISC
|
* 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
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -31,11 +31,6 @@
|
|||||||
#else
|
#else
|
||||||
# include "compat/stdbool.h"
|
# include "compat/stdbool.h"
|
||||||
#endif /* HAVE_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
|
#ifdef HAVE_STRING_H
|
||||||
# include <string.h>
|
# include <string.h>
|
||||||
#endif /* HAVE_STRING_H */
|
#endif /* HAVE_STRING_H */
|
||||||
@ -60,32 +55,15 @@
|
|||||||
|
|
||||||
static int timing_event_adj;
|
static int timing_event_adj;
|
||||||
|
|
||||||
struct iolog_info *
|
static bool
|
||||||
iolog_parse_loginfo(int dfd, const char *iolog_dir)
|
iolog_parse_loginfo_legacy(FILE *fp, const char *iolog_dir,
|
||||||
|
struct iolog_info *li)
|
||||||
{
|
{
|
||||||
char *buf = NULL, *cp, *ep;
|
char *buf = NULL, *cp, *ep;
|
||||||
const char *errstr;
|
const char *errstr;
|
||||||
size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
|
size_t bufsize = 0, cwdsize = 0, cmdsize = 0;
|
||||||
struct iolog_info *li = NULL;
|
bool ret = false;
|
||||||
FILE *fp = NULL;
|
debug_decl(iolog_parse_loginfo_legacy, SUDO_DEBUG_UTIL);
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Info file has three lines:
|
* Info file has three lines:
|
||||||
@ -93,18 +71,12 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
|
|||||||
* 2) cwd
|
* 2) cwd
|
||||||
* 3) command with args
|
* 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 ||
|
if (getdelim(&buf, &bufsize, '\n', fp) == -1 ||
|
||||||
getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 ||
|
getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 ||
|
||||||
getdelim(&li->cmd, &cmdsize, '\n', fp) == -1) {
|
getdelim(&li->cmd, &cmdsize, '\n', fp) == -1) {
|
||||||
sudo_warn(U_("%s: invalid log file"), iolog_dir);
|
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. */
|
/* Strip the newline from the cwd and command. */
|
||||||
li->cwd[strcspn(li->cwd, "\n")] = '\0';
|
li->cwd[strcspn(li->cwd, "\n")] = '\0';
|
||||||
@ -121,20 +93,20 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir)
|
|||||||
/* timestamp */
|
/* timestamp */
|
||||||
if ((ep = strchr(cp, ':')) == NULL) {
|
if ((ep = strchr(cp, ':')) == NULL) {
|
||||||
sudo_warn(U_("%s: time stamp field is missing"), iolog_dir);
|
sudo_warn(U_("%s: time stamp field is missing"), iolog_dir);
|
||||||
goto bad;
|
goto done;
|
||||||
}
|
}
|
||||||
*ep = '\0';
|
*ep = '\0';
|
||||||
li->tstamp.tv_sec = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
|
li->tstamp.tv_sec = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr);
|
||||||
if (errstr != NULL) {
|
if (errstr != NULL) {
|
||||||
sudo_warn(U_("%s: time stamp %s: %s"), iolog_dir, cp, errstr);
|
sudo_warn(U_("%s: time stamp %s: %s"), iolog_dir, cp, errstr);
|
||||||
goto bad;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* submit user */
|
/* submit user */
|
||||||
cp = ep + 1;
|
cp = ep + 1;
|
||||||
if ((ep = strchr(cp, ':')) == NULL) {
|
if ((ep = strchr(cp, ':')) == NULL) {
|
||||||
sudo_warn(U_("%s: user field is missing"), iolog_dir);
|
sudo_warn(U_("%s: user field is missing"), iolog_dir);
|
||||||
goto bad;
|
goto done;
|
||||||
}
|
}
|
||||||
if ((li->user = strndup(cp, (size_t)(ep - cp))) == NULL)
|
if ((li->user = strndup(cp, (size_t)(ep - cp))) == NULL)
|
||||||
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
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;
|
cp = ep + 1;
|
||||||
if ((ep = strchr(cp, ':')) == NULL) {
|
if ((ep = strchr(cp, ':')) == NULL) {
|
||||||
sudo_warn(U_("%s: runas user field is missing"), iolog_dir);
|
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)
|
if ((li->runas_user = strndup(cp, (size_t)(ep - cp))) == NULL)
|
||||||
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
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;
|
cp = ep + 1;
|
||||||
if ((ep = strchr(cp, ':')) == NULL) {
|
if ((ep = strchr(cp, ':')) == NULL) {
|
||||||
sudo_warn(U_("%s: runas group field is missing"), iolog_dir);
|
sudo_warn(U_("%s: runas group field is missing"), iolog_dir);
|
||||||
goto bad;
|
goto done;
|
||||||
}
|
}
|
||||||
if (cp != ep) {
|
if (cp != ep) {
|
||||||
if ((li->runas_group = strndup(cp, (size_t)(ep - cp))) == NULL)
|
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);
|
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:
|
bad:
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
close(fd);
|
close(fd);
|
||||||
if (fp != NULL)
|
if (fp != NULL)
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
free(buf);
|
|
||||||
iolog_free_loginfo(li);
|
iolog_free_loginfo(li);
|
||||||
debug_return_ptr(NULL);
|
debug_return_ptr(NULL);
|
||||||
}
|
}
|
||||||
@ -422,7 +439,19 @@ iolog_read_timing_record(struct iolog_file *iol, struct timing_closure *timing)
|
|||||||
void
|
void
|
||||||
iolog_free_loginfo(struct iolog_info *li)
|
iolog_free_loginfo(struct iolog_info *li)
|
||||||
{
|
{
|
||||||
|
char **p;
|
||||||
|
|
||||||
if (li != NULL) {
|
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->cwd);
|
||||||
free(li->user);
|
free(li->user);
|
||||||
free(li->runas_user);
|
free(li->runas_user);
|
||||||
|
@ -119,6 +119,7 @@ struct search_node {
|
|||||||
#define ST_FROMDATE 7
|
#define ST_FROMDATE 7
|
||||||
#define ST_TODATE 8
|
#define ST_TODATE 8
|
||||||
#define ST_CWD 9
|
#define ST_CWD 9
|
||||||
|
#define ST_HOST 10
|
||||||
char type;
|
char type;
|
||||||
bool negated;
|
bool negated;
|
||||||
bool or;
|
bool or;
|
||||||
@ -126,6 +127,7 @@ struct search_node {
|
|||||||
regex_t cmdre;
|
regex_t cmdre;
|
||||||
struct timespec tstamp;
|
struct timespec tstamp;
|
||||||
char *cwd;
|
char *cwd;
|
||||||
|
char *host;
|
||||||
char *tty;
|
char *tty;
|
||||||
char *user;
|
char *user;
|
||||||
char *runas_group;
|
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]) && \
|
isalnum((unsigned char)(s)[3]) && isalnum((unsigned char)(s)[4]) && \
|
||||||
(s)[5] == '/' && \
|
(s)[5] == '/' && \
|
||||||
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
|
isalnum((unsigned char)(s)[6]) && isalnum((unsigned char)(s)[7]) && \
|
||||||
(s)[8] == '/' && (s)[9] == 'l' && (s)[10] == 'o' && (s)[11] == 'g' && \
|
(s)[8] == '\0')
|
||||||
(s)[12] == '\0')
|
|
||||||
|
|
||||||
__dso_public int main(int argc, char *argv[]);
|
__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;
|
goto bad;
|
||||||
type = ST_RUNASGROUP;
|
type = ST_RUNASGROUP;
|
||||||
break;
|
break;
|
||||||
|
case 'h': /* host */
|
||||||
|
if (strncmp(*av, "host", strlen(*av)) != 0)
|
||||||
|
goto bad;
|
||||||
|
type = ST_HOST;
|
||||||
|
break;
|
||||||
case 'r': /* runas user */
|
case 'r': /* runas user */
|
||||||
if (strncmp(*av, "runas", strlen(*av)) != 0)
|
if (strncmp(*av, "runas", strlen(*av)) != 0)
|
||||||
goto bad;
|
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);
|
res = match_expr(&sn->u.expr, log, matched);
|
||||||
break;
|
break;
|
||||||
case ST_CWD:
|
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;
|
break;
|
||||||
case ST_TTY:
|
case ST_TTY:
|
||||||
res = strcmp(sn->u.tty, log->tty) == 0;
|
if (log->tty != NULL)
|
||||||
|
res = strcmp(sn->u.tty, log->tty) == 0;
|
||||||
break;
|
break;
|
||||||
case ST_RUNASGROUP:
|
case ST_RUNASGROUP:
|
||||||
if (log->runas_group != NULL)
|
if (log->runas_group != NULL)
|
||||||
res = strcmp(sn->u.runas_group, log->runas_group) == 0;
|
res = strcmp(sn->u.runas_group, log->runas_group) == 0;
|
||||||
break;
|
break;
|
||||||
case ST_RUNASUSER:
|
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;
|
break;
|
||||||
case ST_USER:
|
case ST_USER:
|
||||||
res = strcmp(sn->u.user, log->user) == 0;
|
if (log->user != NULL)
|
||||||
|
res = strcmp(sn->u.user, log->user) == 0;
|
||||||
break;
|
break;
|
||||||
case ST_PATTERN:
|
case ST_PATTERN:
|
||||||
rc = regexec(&sn->u.cmdre, log->cmd, 0, NULL, 0);
|
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);
|
li->user, li->tty, li->cwd, li->runas_user);
|
||||||
if (li->runas_group)
|
if (li->runas_group)
|
||||||
printf("GROUP=%s ; ", 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);
|
printf("TSID=%s ; COMMAND=%s\n", idstr, li->cmd);
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user