From ea9b711a70c3ca200c458eedc1aa2bb52c31f26d Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Sun, 29 Mar 2020 05:05:08 -0600 Subject: [PATCH] Write an extended I/O info log in JSON format. This will be used by sudoreplay if it exists to get more information about the command being replayed. --- include/sudo_iolog.h | 9 +- lib/iolog/Makefile.in | 12 +- lib/iolog/iolog_fileio.c | 188 +++++++++++++++++- lib/iolog/iolog_util.c | 5 +- logsrvd/eventlog.c | 2 +- logsrvd/iolog_writer.c | 35 +++- logsrvd/logsrvd.h | 4 +- logsrvd/sendlog.c | 4 +- plugins/sudoers/Makefile.in | 28 +-- plugins/sudoers/iolog.c | 11 +- .../regress/iolog_plugin/check_iolog_plugin.c | 6 +- plugins/sudoers/sudoreplay.c | 13 +- 12 files changed, 270 insertions(+), 47 deletions(-) diff --git a/include/sudo_iolog.h b/include/sudo_iolog.h index 4baba9019..90a65cc1c 100644 --- a/include/sudo_iolog.h +++ b/include/sudo_iolog.h @@ -64,9 +64,14 @@ struct iolog_info { char *runas_group; char *tty; char *cmd; - time_t tstamp; + char *host; + struct timespec tstamp; int lines; int cols; + uid_t runas_uid; + gid_t runas_gid; + char **argv; + char **envp; }; struct timing_closure { @@ -123,7 +128,7 @@ bool iolog_mkpath(char *path); bool iolog_nextid(char *iolog_dir, char sessid[7]); bool iolog_open(struct iolog_file *iol, int dfd, int iofd, const char *mode); bool iolog_rename(const char *from, const char *to); -bool iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, char * const argv[]); +bool iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info); char *iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes, const char **errsttr); const char *iolog_fd_to_name(int iofd); int iolog_openat(int fdf, const char *path, int flags); diff --git a/lib/iolog/Makefile.in b/lib/iolog/Makefile.in index c67d82659..bf9d0bd8e 100644 --- a/lib/iolog/Makefile.in +++ b/lib/iolog/Makefile.in @@ -221,17 +221,17 @@ iolog_fileio.lo: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ - $(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_fileio.c iolog_fileio.i: $(srcdir)/iolog_fileio.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_event.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ - $(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.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 $@ diff --git a/lib/iolog/iolog_fileio.c b/lib/iolog/iolog_fileio.c index 918375d94..a1985d4a2 100644 --- a/lib/iolog/iolog_fileio.c +++ b/lib/iolog/iolog_fileio.c @@ -50,6 +50,7 @@ #include "sudo_conf.h" #include "sudo_debug.h" #include "sudo_event.h" +#include "sudo_json.h" #include "sudo_queue.h" #include "sudo_util.h" #include "sudo_fatal.h" @@ -869,12 +870,12 @@ iolog_gets(struct iolog_file *iol, char *buf, size_t nbytes, } /* - * Write the I/O log file that contains the user and command info. + * Write the legacy I/O log file that contains the user and command info. * This file is not compressed. */ -bool -iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, - char * const argv[]) +static bool +iolog_write_info_file_legacy(int dfd, const char *parent, + struct iolog_info *log_info) { char * const *av; FILE *fp; @@ -896,7 +897,7 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, } fprintf(fp, "%lld:%s:%s:%s:%s:%d:%d\n%s\n", - (long long)log_info->tstamp, + (long long)log_info->tstamp.tv_sec, log_info->user ? log_info->user : "unknown", log_info->runas_user ? log_info->runas_user : RUNAS_DEFAULT, log_info->runas_group ? log_info->runas_group : "", @@ -904,7 +905,7 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, log_info->lines, log_info->cols, log_info->cwd ? log_info->cwd : "unknown"); fputs(log_info->cmd ? log_info->cmd : "unknown", fp); - for (av = argv + 1; *av != NULL; av++) { + for (av = log_info->argv + 1; *av != NULL; av++) { fputc(' ', fp); fputs(*av, fp); } @@ -919,6 +920,181 @@ iolog_write_info_file(int dfd, const char *parent, struct iolog_info *log_info, debug_return_bool(!error); } +/* + * Write the "log.json" file that contains the user and command info. + * This file is not compressed. + */ +static bool +iolog_write_info_file_json(int dfd, const char *parent, struct iolog_info *info) +{ + struct json_container json; + struct json_value json_value; + bool ret = false; + FILE *fp = NULL; + int fd = -1; + debug_decl(iolog_write_info_file_json, SUDO_DEBUG_UTIL); + + if (info->cmd == NULL || info->user == NULL || info->runas_user == NULL) + debug_return_bool(false); + + if (!sudo_json_init(&json, 4, false, false)) + debug_return_bool(false); + + /* Timestamp */ + if (!sudo_json_open_object(&json, "timestamp")) + goto oom; + + json_value.type = JSON_NUMBER; + json_value.u.number = info->tstamp.tv_sec; + if (!sudo_json_add_value(&json, "seconds", &json_value)) + goto oom; + + json_value.type = JSON_NUMBER; + json_value.u.number = info->tstamp.tv_nsec; + if (!sudo_json_add_value(&json, "nanoseconds", &json_value)) + goto oom; + + if (!sudo_json_close_object(&json)) + goto oom; + + json_value.type = JSON_NUMBER; + json_value.u.number = info->cols; + if (!sudo_json_add_value(&json, "columns", &json_value)) + goto oom; + + /* Required */ + json_value.type = JSON_STRING; + json_value.u.string = info->cmd; + if (!sudo_json_add_value(&json, "command", &json_value)) + goto oom; + + json_value.type = JSON_NUMBER; + json_value.u.number = info->lines; + if (!sudo_json_add_value(&json, "lines", &json_value)) + goto oom; + + if (info->argv != NULL) { + json_value.type = JSON_ARRAY; + json_value.u.array = info->argv; + if (!sudo_json_add_value(&json, "runargv", &json_value)) + goto oom; + } + + if (info->envp != NULL) { + json_value.type = JSON_ARRAY; + json_value.u.array = info->envp; + if (!sudo_json_add_value(&json, "runenv", &json_value)) + goto oom; + } + + if (info->runas_group!= NULL) { + if (info->runas_uid != (uid_t)-1) { + json_value.type = JSON_ID; + json_value.u.id = info->runas_gid; + if (!sudo_json_add_value(&json, "rungid", &json_value)) + goto oom; + } + + json_value.type = JSON_STRING; + json_value.u.string = info->runas_group; + if (!sudo_json_add_value(&json, "rungroup", &json_value)) + goto oom; + } + + if (info->runas_uid != (uid_t)-1) { + json_value.type = JSON_ID; + json_value.u.id = info->runas_uid; + if (!sudo_json_add_value(&json, "runuid", &json_value)) + goto oom; + } + + /* Required */ + json_value.type = JSON_STRING; + json_value.u.string = info->runas_user; + if (!sudo_json_add_value(&json, "runuser", &json_value)) + goto oom; + + if (info->cwd != NULL) { + json_value.type = JSON_STRING; + json_value.u.string = info->cwd; + if (!sudo_json_add_value(&json, "submitcwd", &json_value)) + goto oom; + } + + if (info->host != NULL) { + json_value.type = JSON_STRING; + json_value.u.string = info->host; + if (!sudo_json_add_value(&json, "submithost", &json_value)) + goto oom; + } + + /* Required */ + json_value.type = JSON_STRING; + json_value.u.string = info->user; + if (!sudo_json_add_value(&json, "submituser", &json_value)) + goto oom; + + if (info->tty != NULL) { + json_value.type = JSON_STRING; + json_value.u.string = info->tty; + if (!sudo_json_add_value(&json, "ttyname", &json_value)) + goto oom; + } + + fd = iolog_openat(dfd, "log.json", O_CREAT|O_TRUNC|O_WRONLY); + if (fd == -1 || (fp = fdopen(fd, "w")) == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to open %s/log.json", parent); + goto done; + } + fd = -1; + if (fchown(fd, iolog_uid, iolog_gid) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: unable to fchown %d:%d %s/log", __func__, + (int)iolog_uid, (int)iolog_gid, parent); + } + + fprintf(fp, "{%s\n}\n", sudo_json_get_buf(&json)); + fflush(fp); + if (ferror(fp)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to write to I/O log file %s/log.json", parent); + goto done; + } + + ret = true; + goto done; + +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); +done: + sudo_json_free(&json); + if (fp != NULL) + fclose(fp); + if (fd != -1) + close(fd); + + debug_return_bool(ret); +} + +/* + * Write the I/O log and log.json files that contain user and command info. + * These files are not compressed. + */ +bool +iolog_write_info_file(int dfd, const char *parent, + struct iolog_info *log_info) +{ + debug_decl(iolog_write_info_file, SUDO_DEBUG_UTIL); + + if (!iolog_write_info_file_legacy(dfd, parent, log_info)) + debug_return_bool(false); + if (!iolog_write_info_file_json(dfd, parent, log_info)) + debug_return_bool(false); + + debug_return_bool(true); +} + /* * Map IOFD_* -> name. */ diff --git a/lib/iolog/iolog_util.c b/lib/iolog/iolog_util.c index c731ee12b..f3bae0275 100644 --- a/lib/iolog/iolog_util.c +++ b/lib/iolog/iolog_util.c @@ -95,6 +95,8 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir) */ 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) { @@ -122,7 +124,7 @@ iolog_parse_loginfo(int dfd, const char *iolog_dir) goto bad; } *ep = '\0'; - li->tstamp = sudo_strtonum(cp, 0, TIME_T_MAX, &errstr); + 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; @@ -427,6 +429,7 @@ iolog_free_loginfo(struct iolog_info *li) free(li->runas_group); free(li->tty); free(li->cmd); + free(li->host); free(li); } } diff --git a/logsrvd/eventlog.c b/logsrvd/eventlog.c index d35fc93b9..ab48e642a 100644 --- a/logsrvd/eventlog.c +++ b/logsrvd/eventlog.c @@ -552,7 +552,7 @@ do_logfile_sudo(const char *reason, const struct iolog_details *details) goto done; } - if ((timeptr = localtime(&details->submit_time)) != NULL) { + if ((timeptr = localtime(&details->submit_time.tv_sec)) != NULL) { /* strftime() does not guarantee to NUL-terminate so we must check. */ timebuf[sizeof(timebuf) - 1] = '\0'; if (strftime(timebuf, sizeof(timebuf), timefmt, timeptr) != 0 && diff --git a/logsrvd/iolog_writer.c b/logsrvd/iolog_writer.c index d0a59ac33..30cd9c8cb 100644 --- a/logsrvd/iolog_writer.c +++ b/logsrvd/iolog_writer.c @@ -145,11 +145,14 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time, memset(details, 0, sizeof(*details)); /* Submit time. */ - details->submit_time = submit_time->tv_sec; + details->submit_time.tv_sec = submit_time->tv_sec; + details->submit_time.tv_nsec = submit_time->tv_nsec; /* Default values */ details->lines = 24; details->columns = 80; + details->runuid = (uid_t)-1; + details->rungid = (gid_t)-1; /* Pull out values by key from info array. */ for (idx = 0; idx < infolen; idx++) { @@ -211,6 +214,18 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time, } continue; } + if (strcmp(key, "rungid") == 0) { + if (!has_numval(info)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "rungid specified but not a number"); + } else if (info->numval <= 0 || info->numval > INT_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "rungid (%" PRId64 ") out of range", info->numval); + } else { + details->rungid = info->numval; + } + continue; + } if (strcmp(key, "rungroup") == 0) { if (has_strval(info)) { if ((details->rungroup = strdup(info->strval)) == NULL) { @@ -225,6 +240,18 @@ iolog_details_fill(struct iolog_details *details, TimeSpec *submit_time, } continue; } + if (strcmp(key, "runuid") == 0) { + if (!has_numval(info)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "runuid specified but not a number"); + } else if (info->numval <= 0 || info->numval > INT_MAX) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "runuid (%" PRId64 ") out of range", info->numval); + } else { + details->runuid = info->numval; + } + continue; + } if (strcmp(key, "runuser") == 0) { if (has_strval(info)) { if ((details->runuser = strdup(info->strval)) == NULL) { @@ -595,12 +622,16 @@ iolog_details_write(struct iolog_details *details, log_info.runas_group = details->rungroup; log_info.tty = details->ttyname; log_info.cmd = details->command; + log_info.host = details->submithost; log_info.tstamp = details->submit_time; log_info.lines = details->lines; log_info.cols = details->columns; + log_info.runas_uid = details->runuid; + log_info.runas_gid = details->rungid; + log_info.argv = details->argv; debug_return_bool(iolog_write_info_file(closure->iolog_dir_fd, - details->iolog_path, &log_info, details->argv)); + details->iolog_path, &log_info)); } static bool diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index d34b0e66b..7b9f6b551 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -59,10 +59,12 @@ struct iolog_details { char *ttyname; char **argv; char **env_add; - time_t submit_time; + struct timespec submit_time; int argc; int lines; int columns; + uid_t runuid; + gid_t rungid; char sessid[7]; }; diff --git a/logsrvd/sendlog.c b/logsrvd/sendlog.c index 7433df678..e47f02cd7 100644 --- a/logsrvd/sendlog.c +++ b/logsrvd/sendlog.c @@ -631,8 +631,8 @@ fmt_accept_message(struct client_closure *closure) } /* Sudo I/O logs only store start time in seconds. */ - tv.tv_sec = log_info->tstamp; - tv.tv_nsec = 0; + tv.tv_sec = log_info->tstamp.tv_sec; + tv.tv_nsec = log_info->tstamp.tv_nsec; accept_msg.submit_time = &tv; /* Client will send IoBuffer messages. */ diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 5c3281780..00259810a 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -1828,24 +1828,24 @@ match_command.plog: match_command.i match_digest.lo: $(srcdir)/match_digest.c $(devdir)/def_data.h \ $(devdir)/gram.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ - $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ - $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ - $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ - $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/match_digest.c match_digest.i: $(srcdir)/match_digest.c $(devdir)/def_data.h \ $(devdir)/gram.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ - $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ - $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ - $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ - $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ - $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_debug.h $(incdir)/sudo_digest.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(CC) -E -o $@ $(CPPFLAGS) $< match_digest.plog: match_digest.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/match_digest.c --i-file $< --output-file $@ diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index 029c96e31..e7b15369b 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -490,17 +490,22 @@ write_info_log(int dfd, char *iolog_dir, struct iolog_details *details) /* XXX - just use iolog_info in the first place? */ memset(&iolog_info, 0, sizeof(iolog_info)); - time(&iolog_info.tstamp); + iolog_info.cwd = (char *)details->cwd; iolog_info.user = (char *)details->user; iolog_info.runas_user = details->runas_pw->pw_name; iolog_info.runas_group = details->runas_gr ? details->runas_gr->gr_name: NULL; iolog_info.tty = (char *)details->tty; - iolog_info.cwd = (char *)details->cwd; iolog_info.cmd = (char *)details->command; + iolog_info.host = (char *)details->host; + sudo_gettime_real(&iolog_info.tstamp); iolog_info.lines = details->lines; iolog_info.cols = details->cols; + iolog_info.runas_uid = details->runas_pw->pw_uid; + iolog_info.runas_gid = details->runas_gr ? details->runas_gr->gr_gid: (gid_t)-1; + iolog_info.argv = (char **)details->argv; + iolog_info.envp = (char **)details->user_env; - if (!iolog_write_info_file(dfd, iolog_dir, &iolog_info, details->argv)) { + if (!iolog_write_info_file(dfd, iolog_dir, &iolog_info)) { log_warning(SLOG_SEND_MAIL, N_("unable to write to I/O log file: %s"), strerror(errno)); warned = true; diff --git a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c index 79437ef90..7f2ad8dc2 100644 --- a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c +++ b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2018-2019 Todd C. Miller + * Copyright (c) 2018-2020 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 @@ -133,9 +133,9 @@ validate_iolog_info(const char *log_dir) return false; } - if (info->tstamp < now - 10 || info->tstamp > now + 10) { + if (info->tstamp.tv_sec < now - 10 || info->tstamp.tv_sec > now + 10) { sudo_warnx("bad tstamp: want %lld got %lld", (long long)now, - (long long)info->tstamp); + (long long)info->tstamp.tv_sec); return false; } diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index f688b0997..353fa48ea 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -124,7 +124,7 @@ struct search_node { bool or; union { regex_t cmdre; - time_t tstamp; + struct timespec tstamp; char *cwd; char *tty; char *user; @@ -1224,8 +1224,9 @@ parse_expr(struct search_node_list *head, char *argv[], bool sub_expr) if (regcomp(&sn->u.cmdre, *av, REG_EXTENDED|REG_NOSUB) != 0) sudo_fatalx(U_("invalid regular expression: %s"), *av); } else if (type == ST_TODATE || type == ST_FROMDATE) { - sn->u.tstamp = get_date(*av); - if (sn->u.tstamp == -1) + sn->u.tstamp.tv_sec = get_date(*av); + sn->u.tstamp.tv_nsec = 0; + if (sn->u.tstamp.tv_sec == -1) sudo_fatalx(U_("could not parse date \"%s\""), *av); } else { sn->u.ptr = *av; @@ -1283,10 +1284,10 @@ match_expr(struct search_node_list *head, struct iolog_info *log, bool last_matc res = rc == REG_NOMATCH ? 0 : 1; break; case ST_FROMDATE: - res = log->tstamp >= sn->u.tstamp; + res = sudo_timespeccmp(&log->tstamp, &sn->u.tstamp, >=); break; case ST_TODATE: - res = log->tstamp <= sn->u.tstamp; + res = sudo_timespeccmp(&log->tstamp, &sn->u.tstamp, <=); break; default: sudo_fatalx(U_("unknown search type %d"), sn->type); @@ -1332,7 +1333,7 @@ list_session(char *log_dir, regex_t *re, const char *user, const char *tty) idstr = cp; } /* XXX - print lines + cols? */ - timestr = get_timestr(li->tstamp, 1); + timestr = get_timestr(li->tstamp.tv_sec, 1); printf("%s : %s : TTY=%s ; CWD=%s ; USER=%s ; ", timestr ? timestr : "invalid date", li->user, li->tty, li->cwd, li->runas_user);