From cf07dc0757e58a40d56d95fa05694275fb045cb0 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Fri, 5 Oct 2018 14:16:08 -0600 Subject: [PATCH] Add a suspend event type to the I/O log to log suspend/resume of the command so we can skip that delay during replay. --- MANIFEST | 2 +- include/sudo_plugin.h | 5 +- plugins/sudoers/Makefile.in | 24 ++-- plugins/sudoers/iolog.c | 135 +++++++++++++----- plugins/sudoers/iolog.h | 70 ++++++--- plugins/sudoers/iolog_event.h | 33 +++++ plugins/sudoers/iolog_files.h | 47 ++++++ plugins/sudoers/iolog_util.c | 37 +++-- plugins/sudoers/iolog_util.h | 68 --------- .../regress/iolog_plugin/check_iolog_plugin.c | 14 +- .../regress/iolog_util/check_iolog_util.c | 2 +- plugins/sudoers/sudoreplay.c | 97 +++++++------ src/exec_pty.c | 41 +++++- 13 files changed, 378 insertions(+), 197 deletions(-) create mode 100644 plugins/sudoers/iolog_event.h create mode 100644 plugins/sudoers/iolog_files.h delete mode 100644 plugins/sudoers/iolog_util.h diff --git a/MANIFEST b/MANIFEST index 82070142a..338b77a49 100644 --- a/MANIFEST +++ b/MANIFEST @@ -314,9 +314,9 @@ plugins/sudoers/interfaces.c plugins/sudoers/interfaces.h plugins/sudoers/iolog.c plugins/sudoers/iolog.h +plugins/sudoers/iolog_files.h plugins/sudoers/iolog_path.c plugins/sudoers/iolog_util.c -plugins/sudoers/iolog_util.h plugins/sudoers/ldap.c plugins/sudoers/ldap_conf.c plugins/sudoers/ldap_util.c diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index bd22076ea..31d96cc70 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 Todd C. Miller + * Copyright (c) 2009-2018 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 @@ -19,7 +19,7 @@ /* API version major/minor */ #define SUDO_API_VERSION_MAJOR 1 -#define SUDO_API_VERSION_MINOR 12 +#define SUDO_API_VERSION_MINOR 13 #define SUDO_API_MKVERSION(x, y) (((x) << 16) | (y)) #define SUDO_API_VERSION SUDO_API_MKVERSION(SUDO_API_VERSION_MAJOR, SUDO_API_VERSION_MINOR) @@ -170,6 +170,7 @@ struct io_plugin { void (*register_hooks)(int version, int (*register_hook)(struct sudo_hook *hook)); void (*deregister_hooks)(int version, int (*deregister_hook)(struct sudo_hook *hook)); int (*change_winsize)(unsigned int rows, unsigned int cols); + int (*log_suspend)(int signo); }; /* Sudoers group plugin version major/minor */ diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 8ab5a4350..ad67a5826 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -732,15 +732,15 @@ check_iolog_plugin.o: $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ $(incdir)/sudo_util.h $(srcdir)/defaults.h \ - $(srcdir)/iolog_util.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 + $(srcdir)/iolog.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) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c check_iolog_util.o: $(srcdir)/regress/iolog_util/check_iolog_util.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ - $(srcdir)/iolog_util.h $(top_builddir)/config.h + $(srcdir)/iolog.h $(top_builddir)/config.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_util/check_iolog_util.c check_starttime.o: $(srcdir)/regress/starttime/check_starttime.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ @@ -992,10 +992,10 @@ iolog.lo: $(srcdir)/iolog.c $(devdir)/def_data.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)/iolog.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 + $(srcdir)/defaults.h $(srcdir)/iolog.h $(srcdir)/iolog_files.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)/iolog.c iolog_path.lo: $(srcdir)/iolog_path.c $(devdir)/def_data.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ @@ -1010,8 +1010,8 @@ iolog_path.lo: $(srcdir)/iolog_path.c $(devdir)/def_data.h \ iolog_util.lo: $(srcdir)/iolog_util.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ - $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ - $(srcdir)/iolog_util.h $(top_builddir)/config.h + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ + $(top_builddir)/config.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_util.c kerb5.lo: $(authdir)/kerb5.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ @@ -1371,7 +1371,7 @@ sudoreplay.o: $(srcdir)/sudoreplay.c $(incdir)/compat/getopt.h \ $(incdir)/sudo_event.h $(incdir)/sudo_fatal.h \ $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/iolog.h \ - $(srcdir)/iolog_util.h $(srcdir)/logging.h \ + $(srcdir)/iolog_files.h $(srcdir)/logging.h \ $(top_builddir)/config.h $(top_builddir)/pathnames.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/sudoreplay.c testsudoers.o: $(srcdir)/testsudoers.c $(devdir)/def_data.h $(devdir)/gram.h \ diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index 10673453f..ed470ab4a 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -33,12 +33,10 @@ #include #include #include -#ifdef HAVE_ZLIB_H -# include -#endif #include "sudoers.h" #include "iolog.h" +#include "iolog_files.h" /* XXX - separate sudoers.h and iolog.h? */ #undef runas_pw @@ -837,32 +835,32 @@ gzstrerror(gzFile file) * If def_iolog_flush is true, flush the buffer immediately. */ static const char * -iolog_write(const void *buf, unsigned int len, int idx) +iolog_write(union io_fd ifd, const void *buf, unsigned int len) { const char *errstr = NULL; debug_decl(iolog_write, SUDOERS_DEBUG_PLUGIN) #ifdef HAVE_ZLIB_H if (iolog_compress) { - if (gzwrite(io_log_files[idx].fd.g, (const voidp)buf, len) != (int)len) { - errstr = gzstrerror(io_log_files[idx].fd.g); + if (gzwrite(ifd.g, (const voidp)buf, len) != (int)len) { + errstr = gzstrerror(ifd.g); goto done; } if (def_iolog_flush) { - if (gzflush(io_log_files[idx].fd.g, Z_SYNC_FLUSH) != Z_OK) { - errstr = gzstrerror(io_log_files[idx].fd.g); + if (gzflush(ifd.g, Z_SYNC_FLUSH) != Z_OK) { + errstr = gzstrerror(ifd.g); goto done; } } } else #endif { - if (fwrite(buf, 1, len, io_log_files[idx].fd.f) != len) { + if (fwrite(buf, 1, len, ifd.f) != len) { errstr = strerror(errno); goto done; } if (def_iolog_flush) { - if (fflush(io_log_files[idx].fd.f) != 0) { + if (fflush(ifd.f) != 0) { errstr = strerror(errno); goto done; } @@ -1054,7 +1052,7 @@ sudoers_io_version(int verbose) * Returns 1 on success and -1 on error. */ static int -sudoers_io_log(const char *buf, unsigned int len, int idx) +sudoers_io_log(union io_fd ifd, const char *buf, unsigned int len, int event) { struct timespec now, delay; char tbuf[1024]; @@ -1062,9 +1060,9 @@ sudoers_io_log(const char *buf, unsigned int len, int idx) int ret = -1; debug_decl(sudoers_io_log, SUDOERS_DEBUG_PLUGIN) - if (io_log_files[idx].fd.v == NULL) { - sudo_warnx(U_("%s: internal error, file index %d not open"), - __func__, idx); + if (ifd.v == NULL) { + sudo_warnx(U_("%s: internal error, I/O log file for event %d not open"), + __func__, event); debug_return_int(-1); } @@ -1072,24 +1070,24 @@ sudoers_io_log(const char *buf, unsigned int len, int idx) sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, "%s: unable to get time of day", __func__); errstr = strerror(errno); - goto done; + goto bad; } /* Write I/O log file entry. */ - errstr = iolog_write(buf, len, idx); + errstr = iolog_write(ifd, buf, len); if (errstr != NULL) goto done; /* Write timing file entry. */ sudo_timespecsub(&now, &last_time, &delay); len = (unsigned int)snprintf(tbuf, sizeof(tbuf), "%d %lld.%09ld %u\n", - idx, (long long)delay.tv_sec, delay.tv_nsec, len); + event, (long long)delay.tv_sec, delay.tv_nsec, len); if (len >= sizeof(tbuf)) { /* Not actually possible due to the size of tbuf[]. */ errstr = strerror(EOVERFLOW); goto done; } - errstr = iolog_write(tbuf, len, IOFD_TIMING); + errstr = iolog_write(io_log_files[IOFD_TIMING].fd, tbuf, len); if (errstr != NULL) goto done; @@ -1100,6 +1098,7 @@ done: last_time.tv_sec = now.tv_sec; last_time.tv_nsec = now.tv_nsec; +bad: if (ret == -1) { if (errstr != NULL && !warned) { /* Only warn about I/O log file errors once. */ @@ -1117,33 +1116,43 @@ done: } static int -sudoers_io_log_ttyin(const char *buf, unsigned int len) +sudoers_io_log_stdin(const char *buf, unsigned int len) { - return sudoers_io_log(buf, len, IOFD_TTYIN); + const union io_fd ifd = io_log_files[IOFD_STDIN].fd; + + return sudoers_io_log(ifd, buf, len, IO_EVENT_STDIN); } static int -sudoers_io_log_ttyout(const char *buf, unsigned int len) +sudoers_io_log_stdout(const char *buf, unsigned int len) { - return sudoers_io_log(buf, len, IOFD_TTYOUT); + const union io_fd ifd = io_log_files[IOFD_STDOUT].fd; + + return sudoers_io_log(ifd, buf, len, IO_EVENT_STDOUT); } static int -sudoers_io_log_stdin(const char *buf, unsigned int len) +sudoers_io_log_stderr(const char *buf, unsigned int len) { - return sudoers_io_log(buf, len, IOFD_STDIN); + const union io_fd ifd = io_log_files[IOFD_STDERR].fd; + + return sudoers_io_log(ifd, buf, len, IO_EVENT_STDERR); } static int -sudoers_io_log_stdout(const char *buf, unsigned int len) +sudoers_io_log_ttyin(const char *buf, unsigned int len) { - return sudoers_io_log(buf, len, IOFD_STDOUT); + const union io_fd ifd = io_log_files[IOFD_TTYIN].fd; + + return sudoers_io_log(ifd, buf, len, IO_EVENT_TTYIN); } static int -sudoers_io_log_stderr(const char *buf, unsigned int len) +sudoers_io_log_ttyout(const char *buf, unsigned int len) { - return sudoers_io_log(buf, len, IOFD_STDERR); + const union io_fd ifd = io_log_files[IOFD_TTYOUT].fd; + + return sudoers_io_log(ifd, buf, len, IO_EVENT_TTYOUT); } static int @@ -1160,19 +1169,79 @@ sudoers_io_change_winsize(unsigned int lines, unsigned int cols) sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, "%s: unable to get time of day", __func__); errstr = strerror(errno); - goto done; + goto bad; } /* Write window change event to the timing file. */ sudo_timespecsub(&now, &last_time, &delay); len = (unsigned int)snprintf(tbuf, sizeof(tbuf), "%d %lld.%09ld %u %u\n", - IOFD_TIMING, (long long)delay.tv_sec, delay.tv_nsec, lines, cols); + IO_EVENT_WINSIZE, (long long)delay.tv_sec, delay.tv_nsec, lines, cols); + if (len >= sizeof(tbuf)) { + /* Not actually possible due to the size of tbuf[]. */ + errstr = strerror(EOVERFLOW); + goto done; + } + errstr = iolog_write(io_log_files[IOFD_TIMING].fd, tbuf, len); + if (errstr != NULL) + goto done; + + /* Success. */ + ret = 1; + +done: + last_time.tv_sec = now.tv_sec; + last_time.tv_nsec = now.tv_nsec; + +bad: + if (ret == -1) { + if (errstr != NULL && !warned) { + /* Only warn about I/O log file errors once. */ + log_warning(SLOG_SEND_MAIL, + N_("unable to write to I/O log file: %s"), errstr); + warned = true; + } + + /* Ignore errors if they occur if the policy says so. */ + if (iolog_details.ignore_iolog_errors) + ret = 1; + } + + debug_return_int(ret); +} + +static int +sudoers_io_suspend(int signo) +{ + struct timespec now, delay; + unsigned int len; + char tbuf[1024]; + const char *errstr = NULL; + int ret = -1; + debug_decl(sudoers_io_suspend, SUDOERS_DEBUG_PLUGIN) + + if (signo <= 0) { + sudo_warnx(U_("%s: internal error, invalid signal %d"), + __func__, signo); + debug_return_int(-1); + } + + if (sudo_gettime_awake(&now) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, + "%s: unable to get time of day", __func__); + errstr = strerror(errno); + goto bad; + } + + /* Write suspend event to the timing file. */ + sudo_timespecsub(&now, &last_time, &delay); + len = (unsigned int)snprintf(tbuf, sizeof(tbuf), "%d %lld.%09ld %d\n", + IO_EVENT_SUSPEND, (long long)delay.tv_sec, delay.tv_nsec, signo); if (len >= sizeof(tbuf)) { /* Not actually possible due to the size of tbuf[]. */ errstr = strerror(EOVERFLOW); goto done; } - errstr = iolog_write(tbuf, len, IOFD_TIMING); + errstr = iolog_write(io_log_files[IOFD_TIMING].fd, tbuf, len); if (errstr != NULL) goto done; @@ -1183,6 +1252,7 @@ done: last_time.tv_sec = now.tv_sec; last_time.tv_nsec = now.tv_nsec; +bad: if (ret == -1) { if (errstr != NULL && !warned) { /* Only warn about I/O log file errors once. */ @@ -1212,5 +1282,6 @@ __dso_public struct io_plugin sudoers_io = { sudoers_io_log_stderr, NULL, /* register_hooks */ NULL, /* deregister_hooks */ - sudoers_io_change_winsize + sudoers_io_change_winsize, + sudoers_io_suspend }; diff --git a/plugins/sudoers/iolog.h b/plugins/sudoers/iolog.h index d614b2c62..f2748b4fb 100644 --- a/plugins/sudoers/iolog.h +++ b/plugins/sudoers/iolog.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Todd C. Miller + * Copyright (c) 2009-2018 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 @@ -17,17 +17,22 @@ #ifndef SUDOERS_IOLOG_H #define SUDOERS_IOLOG_H +#ifdef HAVE_ZLIB_H +# include /* for gzFile */ +#endif + /* - * I/O log fd numbers as stored in the timing file. - * Changing these will result in incompatible I/O log files! + * I/O log event types as stored as the first field in the timing file. + * Changing existing values will result in incompatible I/O log files. */ -#define IOFD_STDIN 0 -#define IOFD_STDOUT 1 -#define IOFD_STDERR 2 -#define IOFD_TTYIN 3 -#define IOFD_TTYOUT 4 -#define IOFD_TIMING 5 -#define IOFD_MAX 6 +#define IO_EVENT_STDIN 0 +#define IO_EVENT_STDOUT 1 +#define IO_EVENT_STDERR 2 +#define IO_EVENT_TTYIN 3 +#define IO_EVENT_TTYOUT 4 +#define IO_EVENT_WINSIZE 5 +#define IO_EVENT_SUSPEND 6 +#define IO_EVENT_COUNT 7 /* Default maximum session ID */ #define SESSID_MAX 2176782336U @@ -40,20 +45,41 @@ union io_fd { void *v; }; -struct io_log_file { - bool enabled; - const char *suffix; - union io_fd fd; +/* + * Info present in the I/O log file + */ +struct log_info { + char *cwd; + char *user; + char *runas_user; + char *runas_group; + char *tty; + char *cmd; + time_t tstamp; + int rows; + int cols; }; -static struct io_log_file io_log_files[] = { - { false, "/stdin" }, /* IOFD_STDIN */ - { false, "/stdout" }, /* IOFD_STDOUT */ - { false, "/stderr" }, /* IOFD_STDERR */ - { false, "/ttyin" }, /* IOFD_TTYIN */ - { false, "/ttyout" }, /* IOFD_TTYOUT */ - { true, "/timing" }, /* IOFD_TIMING */ - { false, NULL } /* IOFD_MAX */ +struct timing_closure { + const char *decimal; + struct timespec *max_delay; + union io_fd fd; + int event; + union { + struct { + int rows; + int cols; + } winsize; + size_t nbytes; // XXX + int signo; + } u; }; +/* iolog_util.c */ +bool parse_timing(const char *buf, struct timespec *delay, struct timing_closure *timing); +char *parse_delay(const char *cp, struct timespec *delay, const char *decimal_point); +struct log_info *parse_logfile(const char *logfile); +void free_log_info(struct log_info *li); +void adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor); + #endif /* SUDOERS_IOLOG_H */ diff --git a/plugins/sudoers/iolog_event.h b/plugins/sudoers/iolog_event.h new file mode 100644 index 000000000..bd3e9b42f --- /dev/null +++ b/plugins/sudoers/iolog_event.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SUDOERS_IOLOG_EVENT_H +#define SUDOERS_IOLOG_EVENT_H + +/* + * I/O log event types as stored as the first field in the timing file. + * Changing existing values will result in incompatible I/O log files. + */ +#define IO_EVENT_STDIN 0 +#define IO_EVENT_STDOUT 1 +#define IO_EVENT_STDERR 2 +#define IO_EVENT_TTYIN 3 +#define IO_EVENT_TTYOUT 4 +#define IO_EVENT_WINSIZE 5 +#define IO_EVENT_SUSPEND 6 +#define IO_EVENT_COUNT 7 + +#endif /* SUDOERS_IOLOG_EVENT_H */ diff --git a/plugins/sudoers/iolog_files.h b/plugins/sudoers/iolog_files.h new file mode 100644 index 000000000..889c20b3f --- /dev/null +++ b/plugins/sudoers/iolog_files.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SUDOERS_IOLOG_FILES_H +#define SUDOERS_IOLOG_FILES_H + +/* + * Indexes into io_log_files[] + */ +#define IOFD_STDIN 0 +#define IOFD_STDOUT 1 +#define IOFD_STDERR 2 +#define IOFD_TTYIN 3 +#define IOFD_TTYOUT 4 +#define IOFD_TIMING 5 +#define IOFD_MAX 6 + +struct io_log_file { + bool enabled; + const char *suffix; + union io_fd fd; +}; + +static struct io_log_file io_log_files[] = { + { false, "/stdin" }, /* IOFD_STDIN */ + { false, "/stdout" }, /* IOFD_STDOUT */ + { false, "/stderr" }, /* IOFD_STDERR */ + { false, "/ttyin" }, /* IOFD_TTYIN */ + { false, "/ttyout" }, /* IOFD_TTYOUT */ + { true, "/timing" }, /* IOFD_TIMING */ + { false, NULL } /* IOFD_MAX */ +}; + +#endif /* SUDOERS_IOLOG_H */ diff --git a/plugins/sudoers/iolog_util.c b/plugins/sudoers/iolog_util.c index fe11e18b8..e3bb2e2e3 100644 --- a/plugins/sudoers/iolog_util.c +++ b/plugins/sudoers/iolog_util.c @@ -48,9 +48,9 @@ #include "sudo_fatal.h" #include "sudo_debug.h" #include "sudo_util.h" -#include "iolog_util.h" +#include "iolog.h" -static int timing_idx_adj; +static int timing_event_adj; struct log_info * parse_logfile(const char *logfile) @@ -288,8 +288,10 @@ parse_delay(const char *cp, struct timespec *delay, const char *decimal_point) /* * Parse a timing line, which is formatted as: - * index sleep_time num_bytes - * Where index is IOFD_*, sleep_time is the number of seconds to sleep + * IO_EVENT_TTYOUT sleep_time num_bytes + * IO_EVENT_WINSIZE sleep_time rows cols + * IO_EVENT_SUSPEND sleep_time signo + * Where type is IO_EVENT_*, sleep_time is the number of seconds to sleep * before writing the data and num_bytes is the number of bytes to output. * Returns true on success and false on failure. */ @@ -301,17 +303,20 @@ parse_timing(const char *buf, struct timespec *delay, char *cp, *ep; debug_decl(parse_timing, SUDO_DEBUG_UTIL) - /* Parse index */ + /* Clear fd. */ + timing->fd.v = NULL; + + /* Parse event type. */ ulval = strtoul(buf, &ep, 10); if (ep == buf || !isspace((unsigned char) *ep)) goto bad; - if (ulval >= IOFD_MAX) { + if (ulval >= IO_EVENT_COUNT) { if (ulval != 6) goto bad; /* work around a bug in timing files generated by sudo 1.8.7 */ - timing_idx_adj = 2; + timing_event_adj = 2; } - timing->idx = (int)ulval - timing_idx_adj; + timing->event = (int)ulval - timing_event_adj; for (cp = ep + 1; isspace((unsigned char) *cp); cp++) continue; @@ -319,7 +324,17 @@ parse_timing(const char *buf, struct timespec *delay, if ((cp = parse_delay(cp, delay, timing->decimal)) == NULL) goto bad; - if (timing->idx == IOFD_TIMING) { + switch (timing->event) { + case IO_EVENT_SUSPEND: + errno = 0; + ulval = strtoul(cp, &ep, 10); + if (ep == cp || *ep != '\0') + goto bad; + if (ulval > INT_MAX) + goto bad; + timing->u.signo = (int)ulval; + break; + case IO_EVENT_WINSIZE: errno = 0; ulval = strtoul(cp, &ep, 10); if (ep == cp || !isspace((unsigned char) *ep)) @@ -337,7 +352,8 @@ parse_timing(const char *buf, struct timespec *delay, if (ulval > INT_MAX || (errno == ERANGE && ulval == ULONG_MAX)) goto bad; timing->u.winsize.cols = (int)ulval; - } else { + break; + default: errno = 0; ulval = strtoul(cp, &ep, 10); if (ep == cp || *ep != '\0') @@ -345,6 +361,7 @@ parse_timing(const char *buf, struct timespec *delay, if (ulval > SIZE_MAX || (errno == ERANGE && ulval == ULONG_MAX)) goto bad; timing->u.nbytes = (size_t)ulval; + break; } debug_return_bool(true); diff --git a/plugins/sudoers/iolog_util.h b/plugins/sudoers/iolog_util.h deleted file mode 100644 index 5d8cbc4e6..000000000 --- a/plugins/sudoers/iolog_util.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009-2018 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SUDOERS_IOLOG_UTIL_H -#define SUDOERS_IOLOG_UTIL_H - -/* - * Info present in the I/O log file - */ -struct log_info { - char *cwd; - char *user; - char *runas_user; - char *runas_group; - char *tty; - char *cmd; - time_t tstamp; - int rows; - int cols; -}; - -struct timing_closure { - const char *decimal; - struct timespec *max_delay; - int idx; - union { - struct { - int rows; - int cols; - } winsize; - size_t nbytes; // XXX - } u; -}; - -/* - * I/O log fd numbers as stored in the timing file. - * This list must be kept in sync with iolog.h. - */ -#ifndef IOFD_MAX -# define IOFD_STDIN 0 -# define IOFD_STDOUT 1 -# define IOFD_STDERR 2 -# define IOFD_TTYIN 3 -# define IOFD_TTYOUT 4 -# define IOFD_TIMING 5 -# define IOFD_MAX 6 -#endif - -bool parse_timing(const char *buf, struct timespec *delay, struct timing_closure *timing); -char *parse_delay(const char *cp, struct timespec *delay, const char *decimal_point); -struct log_info *parse_logfile(const char *logfile); -void free_log_info(struct log_info *li); -void adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor); - -#endif /* SUDOERS_IOLOG_UTIL_H */ diff --git a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c index 12ca17121..456ed3c35 100644 --- a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c +++ b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c @@ -35,7 +35,7 @@ #include "sudoers.h" #include "def_data.c" /* for iolog_path.c */ #include "sudo_plugin.h" -#include "iolog_util.h" +#include "iolog.h" extern struct io_plugin sudoers_io; @@ -158,12 +158,12 @@ validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2) sudo_warnx("invalid timing file line: %s", buf); return false; } - if (timing.idx != type) { + if (timing.event != type) { sudo_warnx("record %d: want type %d, got type %d", recno, type, - timing.idx); + timing.event); return false; } - if (type == IOFD_TIMING) { + if (type == IO_EVENT_WINSIZE) { if (timing.u.winsize.rows != (int)p1) { sudo_warnx("record %d: want %u rows, got %u", recno, p1, timing.u.winsize.rows); @@ -295,19 +295,19 @@ test_endpoints(int *ntests, int *nerrors, const char *iolog_dir, char *envp[]) } /* Line 1: output of id command. */ - if (!validate_timing(fp, 1, IOFD_TTYOUT, strlen(output), 0)) { + if (!validate_timing(fp, 1, IO_EVENT_TTYOUT, strlen(output), 0)) { (*nerrors)++; return; } /* Line 2: window size change. */ - if (!validate_timing(fp, 2, IOFD_TIMING, 32, 128)) { + if (!validate_timing(fp, 2, IO_EVENT_WINSIZE, 32, 128)) { (*nerrors)++; return; } /* Line 3: window size change. */ - if (!validate_timing(fp, 3, IOFD_TIMING, 24, 80)) { + if (!validate_timing(fp, 3, IO_EVENT_WINSIZE, 24, 80)) { (*nerrors)++; return; } diff --git a/plugins/sudoers/regress/iolog_util/check_iolog_util.c b/plugins/sudoers/regress/iolog_util/check_iolog_util.c index d25aed4e3..d9c932d31 100644 --- a/plugins/sudoers/regress/iolog_util/check_iolog_util.c +++ b/plugins/sudoers/regress/iolog_util/check_iolog_util.c @@ -33,7 +33,7 @@ #include "sudo_compat.h" #include "sudo_util.h" #include "sudo_fatal.h" -#include "iolog_util.h" +#include "iolog.h" __dso_public int main(int argc, char *argv[]); diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index 667dbc64b..e3ef614fd 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -49,9 +49,6 @@ #include #include #include -#ifdef HAVE_ZLIB_H -# include -#endif #include @@ -61,7 +58,7 @@ #include "sudo_fatal.h" #include "logging.h" #include "iolog.h" -#include "iolog_util.h" +#include "iolog_files.h" #include "sudo_queue.h" #include "sudo_plugin.h" #include "sudo_conf.h" @@ -154,7 +151,7 @@ static struct option long_opts[] = { { NULL, no_argument, NULL, '\0' }, }; -/* XXX move to separate header? */ +/* XXX move to separate header? (currently in sudoers.h) */ extern char *get_timestr(time_t, int); extern time_t get_date(char *); @@ -189,7 +186,7 @@ __dso_public int main(int argc, char *argv[]); int main(int argc, char *argv[]) { - int ch, idx, plen, exitcode = 0; + int ch, i, plen, exitcode = 0; bool def_filter = true, listonly = false; bool interactive = true, resize = true; const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL; @@ -321,8 +318,8 @@ main(int argc, char *argv[]) plen -= 7; /* Open files for replay, applying replay filter for the -f flag. */ - for (idx = 0; idx < IOFD_MAX; idx++) { - if (open_io_fd(path, plen, &io_log_files[idx]) == -1) + for (i = 0; i < IOFD_MAX; i++) { + if (open_io_fd(path, plen, &io_log_files[i]) == -1) sudo_fatal(U_("unable to open %s"), path); } @@ -359,7 +356,7 @@ done: * Return 0 for EOF or -1 on error. */ static ssize_t -io_log_read(int idx, char *buf, size_t nbytes) +io_log_read(union io_fd ifd, char *buf, size_t nbytes) { ssize_t nread; debug_decl(io_log_read, SUDO_DEBUG_UTIL) @@ -369,39 +366,39 @@ io_log_read(int idx, char *buf, size_t nbytes) debug_return_ssize_t(-1); } #ifdef HAVE_ZLIB_H - nread = gzread(io_log_files[idx].fd.g, buf, nbytes); + nread = gzread(ifd.g, buf, nbytes); #else - nread = (ssize_t)fread(buf, 1, nbytes, io_log_files[idx].fd.f); - if (nread == 0 && ferror(io_log_files[idx].fd.f)) + nread = (ssize_t)fread(buf, 1, nbytes, ifd.f); + if (nread == 0 && ferror(ifd.f)) nread = -1; #endif debug_return_ssize_t(nread); } static int -io_log_eof(int idx) +io_log_eof(union io_fd ifd) { int ret; debug_decl(io_log_eof, SUDO_DEBUG_UTIL) #ifdef HAVE_ZLIB_H - ret = gzeof(io_log_files[idx].fd.g); + ret = gzeof(ifd.g); #else - ret = feof(io_log_files[idx].fd.f); + ret = feof(ifd.f); #endif debug_return_int(ret); } static char * -io_log_gets(int idx, char *buf, size_t nbytes) +io_log_gets(union io_fd ifd, char *buf, size_t nbytes) { char *str; debug_decl(io_log_gets, SUDO_DEBUG_UTIL) #ifdef HAVE_ZLIB_H - str = gzgets(io_log_files[idx].fd.g, buf, nbytes); + str = gzgets(ifd.g, buf, nbytes); #else - str = fgets(buf, nbytes, io_log_files[idx].fd.f); + str = fgets(buf, nbytes, ifd.f); #endif debug_return_str(str); } @@ -748,9 +745,9 @@ read_timing_record(struct replay_closure *closure) debug_decl(read_timing_record, SUDO_DEBUG_UTIL) /* Read next record from timing file. */ - if (io_log_gets(IOFD_TIMING, buf, sizeof(buf)) == NULL) { + if (io_log_gets(io_log_files[IOFD_TIMING].fd, buf, sizeof(buf)) == NULL) { /* EOF or error reading timing file, we are done. */ - debug_return_int(io_log_eof(IOFD_TIMING) ? 1 : -1); + debug_return_int(io_log_eof(io_log_files[IOFD_TIMING].fd) ? 1 : -1); } /* Parse timing file record. */ @@ -760,7 +757,8 @@ read_timing_record(struct replay_closure *closure) /* Record number bytes to read. */ /* XXX - remove timing->nbytes? */ - if (closure->timing.idx != IOFD_TIMING) { + if (closure->timing.event != IO_EVENT_WINSIZE && + closure->timing.event != IO_EVENT_SUSPEND) { closure->iobuf.len = 0; closure->iobuf.off = 0; closure->iobuf.lastc = '\0'; @@ -807,25 +805,24 @@ fill_iobuf(struct replay_closure *closure) { const size_t space = sizeof(closure->iobuf.buf) - closure->iobuf.len; const struct timing_closure *timing = &closure->timing; - ssize_t nread; - size_t len; debug_decl(fill_iobuf, SUDO_DEBUG_UTIL) if (closure->iobuf.toread != 0 && space != 0) { - len = closure->iobuf.toread < space ? closure->iobuf.toread : space; - nread = io_log_read(timing->idx, + const size_t len = + closure->iobuf.toread < space ? closure->iobuf.toread : space; + ssize_t nread = io_log_read(timing->fd, closure->iobuf.buf + closure->iobuf.off, len); if (nread <= 0) { if (nread == 0) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "%s: premature EOF, expected %u bytes", - io_log_files[timing->idx].suffix, closure->iobuf.toread); + io_log_files[timing->event].suffix, closure->iobuf.toread); } else { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO, - "%s: read error", io_log_files[timing->idx].suffix); + "%s: read error", io_log_files[timing->event].suffix); } sudo_warnx(U_("unable to read %s"), - io_log_files[timing->idx].suffix); + io_log_files[timing->event].suffix); debug_return_bool(false); } closure->iobuf.toread -= nread; @@ -844,26 +841,44 @@ static void delay_cb(int fd, int what, void *v) { struct replay_closure *closure = v; - const struct timing_closure *timing = &closure->timing; + struct timing_closure *timing = &closure->timing; debug_decl(delay_cb, SUDO_DEBUG_UTIL) - /* Check for window change event and resize as needed. */ - if (timing->idx == IOFD_TIMING) { + switch (timing->event) { + case IO_EVENT_WINSIZE: resize_terminal(timing->u.winsize.rows, timing->u.winsize.cols); - next_timing_record(closure); - debug_return; + break; + case IO_EVENT_STDIN: + if (io_log_files[IOFD_STDIN].enabled) + timing->fd = io_log_files[IOFD_STDIN].fd; + break; + case IO_EVENT_STDOUT: + if (io_log_files[IOFD_STDOUT].enabled) + timing->fd = io_log_files[IOFD_STDOUT].fd; + break; + case IO_EVENT_STDERR: + if (io_log_files[IOFD_STDERR].enabled) + timing->fd = io_log_files[IOFD_STDERR].fd; + break; + case IO_EVENT_TTYIN: + if (io_log_files[IOFD_TTYIN].enabled) + timing->fd = io_log_files[IOFD_TTYIN].fd; + break; + case IO_EVENT_TTYOUT: + if (io_log_files[IOFD_TTYOUT].enabled) + timing->fd = io_log_files[IOFD_TTYOUT].fd; + break; } - /* If we are not replaying this stream, just read the next record. */ - if (timing->idx >= IOFD_MAX || !io_log_files[timing->idx].enabled) { + if (timing->fd.v != NULL) { + /* If the stream is open, enable the write event. */ + if (sudo_ev_add(closure->evbase, closure->output_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); + } else { + /* Not replaying, get the next timing record and continue. */ next_timing_record(closure); - debug_return; } - /* We are replaying this strean, enable write event. */ - if (sudo_ev_add(closure->evbase, closure->output_ev, NULL, false) == -1) - sudo_fatal(U_("unable to add event to queue")); - debug_return; } @@ -1065,7 +1080,7 @@ write_output(int fd, int what, void *v) iov[0].iov_len = nbytes; if (closure->interactive && - (timing->idx == IOFD_STDOUT || timing->idx == IOFD_STDERR)) { + (timing->event == IO_EVENT_STDOUT || timing->event == IO_EVENT_STDERR)) { char *nl; /* diff --git a/src/exec_pty.c b/src/exec_pty.c index b89afd87c..4c26479ec 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -377,7 +377,39 @@ log_stderr(const char *buf, unsigned int n, struct io_buffer *iob) debug_return_bool(ret); } -/* Call I/O plugin stderr log method. */ +/* Call I/O plugin suspend log method. */ +static void +log_suspend(int signo) +{ + struct plugin_container *plugin; + sigset_t omask; + debug_decl(log_suspend, SUDO_DEBUG_EXEC); + + sigprocmask(SIG_BLOCK, &ttyblock, &omask); + TAILQ_FOREACH(plugin, &io_plugins, entries) { + if (plugin->u.io->version < SUDO_API_MKVERSION(1, 13)) + continue; + if (plugin->u.io->log_suspend) { + int rc; + + sudo_debug_set_active_instance(plugin->debug_instance); + rc = plugin->u.io->log_suspend(signo); + if (rc <= 0) { + if (rc < 0) { + /* Error: disable plugin's I/O function. */ + plugin->u.io->log_suspend = NULL; + } + break; + } + } + } + sudo_debug_set_active_instance(sudo_debug_instance); + sigprocmask(SIG_SETMASK, &omask, NULL); + + debug_return; +} + +/* Call I/O plugin window change log method. */ static void log_winchange(unsigned int rows, unsigned int cols) { @@ -469,6 +501,9 @@ suspend_sudo(struct exec_closure_pty *ec, int signo) if (ttymode != TERM_COOKED) sudo_term_restore(io_fds[SFD_USERTTY], false); + /* Log the suspend event. */ + log_suspend(signo); + if (sig2str(signo, signame) == -1) snprintf(signame, sizeof(signame), "%d", signo); @@ -485,6 +520,9 @@ suspend_sudo(struct exec_closure_pty *ec, int signo) if (killpg(ec->ppgrp, signo) != 0) sudo_warn("killpg(%d, SIG%s)", (int)ec->ppgrp, signame); + /* Log the resume event. */ + log_suspend(SIGCONT); + /* Check foreground/background status on resume. */ check_foreground(ec); @@ -513,6 +551,7 @@ suspend_sudo(struct exec_closure_pty *ec, int signo) if (sudo_sigaction(signo, &osa, NULL) != 0) sudo_warn(U_("unable to restore handler for signal %d"), signo); } + ret = ttymode == TERM_RAW ? SIGCONT_FG : SIGCONT_BG; break; } -- 2.40.0