From ff5ac3ef0e72e280a941b9164997b361e91593b0 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 11 Jan 2018 10:49:20 -0700 Subject: [PATCH] Add tsdump, a simple utility to dump a timestamp file. To build, run "make tsdump" in the plugins/sudoers directory (it is not built by default). In order to map the tty device number to a name, sudo_ttyname_dev() has been moved into libsudo_util. --- MANIFEST | 2 + config.h.in | 3 + configure | 13 +- configure.ac | 4 +- include/sudo_util.h | 4 + lib/util/Makefile.in | 10 +- lib/util/ttyname_dev.c | 311 ++++++++++++++++++++++++++++++++++++ lib/util/util.exp.in | 1 + plugins/sudoers/Makefile.in | 25 +-- plugins/sudoers/check.h | 14 ++ plugins/sudoers/tsdump.c | 294 ++++++++++++++++++++++++++++++++++ src/ttyname.c | 270 +------------------------------ 12 files changed, 668 insertions(+), 283 deletions(-) create mode 100644 lib/util/ttyname_dev.c create mode 100644 plugins/sudoers/tsdump.c diff --git a/MANIFEST b/MANIFEST index 4bd2e85ab..66bd8f0eb 100644 --- a/MANIFEST +++ b/MANIFEST @@ -178,6 +178,7 @@ lib/util/sudo_conf.c lib/util/sudo_debug.c lib/util/sudo_dso.c lib/util/term.c +lib/util/ttyname_dev.c lib/util/ttysize.c lib/util/util.exp.in lib/util/utimens.c @@ -540,6 +541,7 @@ plugins/sudoers/toke.c plugins/sudoers/toke.h plugins/sudoers/toke.l plugins/sudoers/toke_util.c +plugins/sudoers/tsdump.c plugins/sudoers/tsgetgrpw.c plugins/sudoers/tsgetgrpw.h plugins/sudoers/visudo.c diff --git a/config.h.in b/config.h.in index 922fbeb3c..d97ba0a9f 100644 --- a/config.h.in +++ b/config.h.in @@ -195,6 +195,9 @@ don't. */ #undef HAVE_DECL__SYS_SIGNAME +/* Define to 1 if you have the `devname' function. */ +#undef HAVE_DEVNAME + /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_DIRENT_H diff --git a/configure b/configure index b97bf34dc..fe28b90c7 100755 --- a/configure +++ b/configure @@ -19252,7 +19252,18 @@ if test "x$ac_cv_func_sysctl" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_SYSCTL 1 _ACEOF - ac_fn_c_check_member "$LINENO" "struct kinfo_proc" "ki_tdev" "ac_cv_member_struct_kinfo_proc_ki_tdev" " + for ac_func in devname +do : + ac_fn_c_check_func "$LINENO" "devname" "ac_cv_func_devname" +if test "x$ac_cv_func_devname" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DEVNAME 1 +_ACEOF + +fi +done + + ac_fn_c_check_member "$LINENO" "struct kinfo_proc" "ki_tdev" "ac_cv_member_struct_kinfo_proc_ki_tdev" " # include # include # include diff --git a/configure.ac b/configure.ac index 9bfc970e4..97d0959c0 100644 --- a/configure.ac +++ b/configure.ac @@ -2528,8 +2528,8 @@ if test "$utmp_style" = "LEGACY"; then AC_CHECK_FUNCS([fseeko]) fi -AC_CHECK_FUNCS([sysctl], [AC_CHECK_MEMBERS([struct kinfo_proc.ki_tdev], [], - [ +AC_CHECK_FUNCS([sysctl], [AC_CHECK_FUNCS([devname]) + AC_CHECK_MEMBERS([struct kinfo_proc.ki_tdev], [], [ AC_CHECK_MEMBERS([struct kinfo_proc2.p_tdev], [], [ AC_CHECK_MEMBERS([struct kinfo_proc.p_tdev], [], [ AC_CHECK_MEMBERS([struct kinfo_proc.kp_eproc.e_tdev], [], [], [ diff --git a/include/sudo_util.h b/include/sudo_util.h index 7e241b843..c7218b228 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -248,6 +248,10 @@ __dso_public bool sudo_term_raw_v1(int fd, int isig); __dso_public bool sudo_term_restore_v1(int fd, bool flush); #define sudo_term_restore(_a, _b) sudo_term_restore_v1((_a), (_b)) +/* ttyname_dev.c */ +__dso_public char *sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen); +#define sudo_ttyname_dev(_a, _b, _c) sudo_ttyname_dev_v1((_a), (_b), (_c)) + /* ttysize.c */ __dso_public void sudo_get_ttysize_v1(int *rowp, int *colp); #define sudo_get_ttysize(_a, _b) sudo_get_ttysize_v1((_a), (_b)) diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index 211a001fe..9f6bb747d 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -111,8 +111,8 @@ SHELL = @SHELL@ LTOBJS = event.lo fatal.lo key_val.lo gethostname.lo gettime.lo \ gidlist.lo lbuf.lo locking.lo parseln.lo progname.lo secure_path.lo \ setgroups.lo strsplit.lo strtobool.lo strtoid.lo strtomode.lo \ - sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo ttysize.lo \ - @COMMON_OBJS@ @LTLIBOBJS@ + sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo ttyname_dev.lo \ + ttysize.lo @COMMON_OBJS@ @LTLIBOBJS@ ATOFOO_TEST_OBJS = atofoo_test.lo @@ -589,6 +589,12 @@ term.lo: $(srcdir)/term.c $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_debug.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)/term.c +ttyname_dev.lo: $(srcdir)/ttyname_dev.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.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)/ttyname_dev.c ttysize.lo: $(srcdir)/ttysize.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ diff --git a/lib/util/ttyname_dev.c b/lib/util/ttyname_dev.c new file mode 100644 index 000000000..97585eb35 --- /dev/null +++ b/lib/util/ttyname_dev.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2012-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. + */ + +#include + +#include +#include +#if defined(MAJOR_IN_MKDEV) +# include +#elif defined(MAJOR_IN_SYSMACROS) +# include +#else +# include +#endif +#include +#include +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#include +#include +#include +#include +#include + +#include +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_conf.h" +#include "sudo_util.h" + +#if defined(HAVE_DEVNAME) +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * The BSD version uses devname(). + */ +char * +sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen) +{ + char *dev; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* Some versions of devname() return NULL on failure, others do not. */ + dev = devname(tdev, S_IFCHR); + if (dev != NULL && *dev != '?' && *dev != '#') { + if (strlcpy(name, _PATH_DEV, namelen) < namelen && + strlcat(name, dev, namelen) < namelen) + debug_return_str(name); + errno = ERANGE; + } else { + /* Not all versions of devname() set errno. */ + errno = ENOENT; + } + debug_return_str(NULL); +} +#elif defined(HAVE__TTYNAME_DEV) +extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen); + +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * This version is just a wrapper around _ttyname_dev(). + */ +char * +sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen) +{ + int serrno = errno; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* + * _ttyname_dev() sets errno to ERANGE if namelen is too small + * but does not modify it if tdev is not found. + */ + errno = ENOENT; + if (_ttyname_dev(tdev, name, namelen) == NULL) + debug_return_str(NULL); + errno = serrno; + + debug_return_str(name); +} +#else +/* + * Device nodes to ignore. + */ +static const char *ignore_devs[] = { + _PATH_DEV "stdin", + _PATH_DEV "stdout", + _PATH_DEV "stderr", + NULL +}; + +/* + * Do a scan of a directory looking for the specified device. + * Does not descend into subdirectories. + * Returns name on success and NULL on failure, setting errno. + */ +static char * +sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen) +{ + size_t sdlen; + char pathbuf[PATH_MAX]; + char *ret = NULL; + struct dirent *dp; + struct stat sb; + unsigned int i; + DIR *d = NULL; + debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL) + + if (dir[0] == '\0') { + errno = ENOENT; + goto done; + } + if ((d = opendir(dir)) == NULL) + goto done; + + if (fstat(dirfd(d), &sb) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to fstat %s", dir); + goto done; + } + if ((sb.st_mode & S_IWOTH) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "ignoring world-writable directory %s", dir); + errno = ENOENT; + goto done; + } + + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "scanning for dev %u in %s", (unsigned int)rdev, dir); + + sdlen = strlen(dir); + while (sdlen > 0 && dir[sdlen - 1] == '/') + sdlen--; + if (sdlen + 1 >= sizeof(pathbuf)) { + errno = ERANGE; + goto done; + } + memcpy(pathbuf, dir, sdlen); + pathbuf[sdlen++] = '/'; + + while ((dp = readdir(d)) != NULL) { + struct stat sb; + + /* Skip anything starting with "." */ + if (dp->d_name[0] == '.') + continue; + + pathbuf[sdlen] = '\0'; + if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name); + continue; + } + + /* Ignore device nodes listed in ignore_devs[]. */ + for (i = 0; ignore_devs[i] != NULL; i++) { + if (strcmp(pathbuf, ignore_devs[i]) == 0) + break; + } + if (ignore_devs[i] != NULL) { + sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, + "ignoring %s", pathbuf); + continue; + } + +# if defined(HAVE_STRUCT_DIRENT_D_TYPE) + /* + * Avoid excessive stat() calls by checking dp->d_type. + */ + switch (dp->d_type) { + case DT_CHR: + case DT_LNK: + case DT_UNKNOWN: + break; + default: + /* Not a character device or link, skip it. */ + sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, + "skipping non-device %s", pathbuf); + continue; + } +# endif + if (stat(pathbuf, &sb) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, + "unable to stat %s", pathbuf); + continue; + } + if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "resolved dev %u as %s", (unsigned int)rdev, pathbuf); + if (strlcpy(name, pathbuf, namelen) < namelen) { + ret = name; + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to store %s, have %zu, need %zu", + pathbuf, namelen, strlen(pathbuf) + 1); + errno = ERANGE; + } + goto done; + } + } + +done: + if (d != NULL) + closedir(d); + debug_return_str(ret); +} + +static char * +sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen) +{ + struct stat sb; + debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL) + + if (stat(devname, &sb) == 0) { + if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "comparing dev %u to %s: match!", + (unsigned int)rdev, devname); + if (strlcpy(buf, devname, buflen) < buflen) + debug_return_str(buf); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to store %s, have %zu, need %zu", + devname, buflen, strlen(devname) + 1); + errno = ERANGE; + } + } + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "comparing dev %u to %s: no", (unsigned int)rdev, devname); + debug_return_str(NULL); +} + +/* + * Like ttyname() but uses a dev_t instead of an open fd. + * Returns name on success and NULL on failure, setting errno. + * Generic version. + */ +char * +sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen) +{ + const char *devsearch, *devsearch_end; + char path[PATH_MAX], *ret; + const char *cp, *ep; + size_t len; + debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) + + /* + * First, check /dev/console. + */ + ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen); + if (ret != NULL) + goto done; + + /* + * Then check the device search path. + */ + devsearch = sudo_conf_devsearch_path(); + devsearch_end = devsearch + strlen(devsearch); + for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep); + cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) { + + len = (size_t)(ep - cp); + if (len >= sizeof(path)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "devsearch entry %.*s too long", (int)len, cp); + continue; + } + memcpy(path, cp, len); + path[len] = '\0'; + + if (strcmp(path, _PATH_DEV "pts") == 0) { + /* Special case /dev/pts */ + len = (size_t)snprintf(path, sizeof(path), "%spts/%u", + _PATH_DEV, (unsigned int)minor(rdev)); + if (len >= sizeof(path)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "devsearch entry %spts/%u too long", + _PATH_DEV, (unsigned int)minor(rdev)); + continue; + } + ret = sudo_dev_check(rdev, path, buf, buflen); + if (ret != NULL) + goto done; + } else { + /* Scan path, looking for rdev. */ + ret = sudo_ttyname_scan(path, rdev, buf, buflen); + if (ret != NULL || errno == ENOMEM) + goto done; + } + } + +done: + debug_return_str(ret); +} +#endif diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index be13bd9db..869b56c9d 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -60,6 +60,7 @@ sudo_fatal_callback_deregister_v1 sudo_fatal_callback_register_v1 sudo_fatal_nodebug_v1 sudo_fatalx_nodebug_v1 +sudo_ttyname_dev_v1 sudo_get_ttysize_v1 sudo_gethostname_v1 sudo_gettime_mono_v1 diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 4aa9318ac..69ce1b5a3 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -172,6 +172,8 @@ REPLAY_OBJS = getdate.o sudoreplay.o TEST_OBJS = group_plugin.o interfaces.o locale.o net_ifs.o \ sudo_printf.o testsudoers.o tsgetgrpw.o +TSDUMP_OBJS = tsdump.o sudoers_debug.o locale.o + CHECK_ADDR_OBJS = check_addr.o interfaces.o match_addr.o sudoers_debug.o \ sudo_printf.o @@ -245,6 +247,9 @@ sudoreplay: timestr.lo $(REPLAY_OBJS) $(LT_LIBS) testsudoers: libparsesudoers.la $(TEST_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(TEST_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) libparsesudoers.la $(LIBS) $(TESTSUDOERS_LIBS) +tsdump: $(TSDUMP_OBJS) $(LT_LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(TSDUMP_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) + check_addr: $(CHECK_ADDR_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_ADDR_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) $(NET_LIBS) @@ -661,15 +666,9 @@ check_iolog_path.o: $(srcdir)/regress/iolog_path/check_iolog_path.c \ $(top_builddir)/config.h $(top_builddir)/pathnames.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_path/check_iolog_path.c check_starttime.o: $(srcdir)/regress/starttime/check_starttime.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)/check.h $(srcdir)/defaults.h $(srcdir)/logging.h \ - $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(srcdir)/check.h $(top_builddir)/config.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/starttime/check_starttime.c check_symbols.o: $(srcdir)/regress/check_symbols/check_symbols.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ @@ -1250,6 +1249,14 @@ toke_util.lo: $(srcdir)/toke_util.c $(devdir)/def_data.h $(devdir)/gram.h \ $(top_builddir)/config.h $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/toke_util.c toke_util.o: toke_util.lo +tsdump.o: $(srcdir)/tsdump.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)/check.h $(srcdir)/defaults.h $(srcdir)/logging.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)/tsdump.c tsgetgrpw.o: $(srcdir)/tsgetgrpw.c $(devdir)/def_data.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ diff --git a/plugins/sudoers/check.h b/plugins/sudoers/check.h index be7a8996e..11013c834 100644 --- a/plugins/sudoers/check.h +++ b/plugins/sudoers/check.h @@ -46,6 +46,20 @@ #define TS_DISABLED 0x01 /* entry disabled */ #define TS_ANYUID 0x02 /* ignore uid, only valid in the key */ +struct timestamp_entry_v1 { + unsigned short version; /* version number */ + unsigned short size; /* entry size */ + unsigned short type; /* TS_GLOBAL, TS_TTY, TS_PPID */ + unsigned short flags; /* TS_DISABLED, TS_ANYUID */ + uid_t auth_uid; /* uid to authenticate as */ + pid_t sid; /* session ID associated with tty/ppid */ + struct timespec ts; /* time stamp (CLOCK_MONOTONIC) */ + union { + dev_t ttydev; /* tty device number */ + pid_t ppid; /* parent pid */ + } u; +}; + struct timestamp_entry { unsigned short version; /* version number */ unsigned short size; /* entry size */ diff --git a/plugins/sudoers/tsdump.c b/plugins/sudoers/tsdump.c new file mode 100644 index 000000000..4cac78ce1 --- /dev/null +++ b/plugins/sudoers/tsdump.c @@ -0,0 +1,294 @@ +/* + * 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. + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#include +#include +#include +#include +#include + +#include "sudoers.h" +#include "check.h" + +struct timestamp_entry_common { + unsigned short version; /* version number */ + unsigned short size; /* entry size */ + unsigned short type; /* TS_GLOBAL, TS_TTY, TS_PPID */ + unsigned short flags; /* TS_DISABLED, TS_ANYUID */ +}; + +union timestamp_entry_storage { + struct timestamp_entry_common common; + struct timestamp_entry_v1 v1; + struct timestamp_entry v2; +}; + +__dso_public int main(int argc, char *argv[]); + +static void usage(void) __attribute__((__noreturn__)); +static void dump_entry(union timestamp_entry_storage *u, off_t pos); +static bool valid_entry(union timestamp_entry_storage *u, off_t pos); + +/* + * tsdump: a simple utility to dump the contents of a time stamp file. + * Unlock sudo, does not perform any locking of the time stamp file. + */ + +int +main(int argc, char *argv[]) +{ + int ch, fd; + const char *user = NULL; + char *fname = NULL; + union timestamp_entry_storage cur; + debug_decl(main, SUDOERS_DEBUG_MAIN) + +#if defined(SUDO_DEVEL) && defined(__OpenBSD__) + malloc_options = "S"; +#endif + + initprogname(argc > 0 ? argv[0] : "tsdump"); + + bindtextdomain("sudoers", LOCALEDIR); + textdomain("sudoers"); + + /* Initialize the debug subsystem. */ + if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1) + exit(EXIT_FAILURE); + sudoers_debug_register(getprogname(), sudo_conf_debug_files(getprogname())); + + while ((ch = getopt(argc, argv, "f:u:")) != -1) { + switch (ch) { + case 'f': + fname = optarg; + break; + case 'u': + user = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (fname != NULL && user != NULL) { + sudo_warnx("the -f and -u flags are mutually exclusive"); + usage(); + } + + if (fname == NULL) { + struct passwd *pw; + + if (user == NULL) { + if ((pw = getpwuid(geteuid())) == NULL) + sudo_fatalx(U_("unknown uid: %d"), (int)geteuid()); + user = pw->pw_name; + } + if (asprintf(&fname, "%s/%s", _PATH_SUDO_TIMEDIR, user) == -1) + sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + } + + fd = open(fname, O_RDONLY); + if (fd == -1) + sudo_fatal(U_("unable to open %s"), fname); + + for (;;) { + off_t pos = lseek(fd, 0, SEEK_CUR); + ssize_t nread; + bool valid; + + if ((nread = read(fd, &cur, sizeof(cur))) == 0) + break; + if (nread == -1) + sudo_fatal(U_("unable to read %s"), fname); + + valid = valid_entry(&cur, pos); + if (cur.common.size != 0 && cur.common.size != sizeof(cur)) { + off_t offset = (off_t)cur.common.size - (off_t)sizeof(cur); + if (lseek(fd, offset, SEEK_CUR) == -1) + sudo_fatal("unable to seek %d bytes", (int)offset); + } + if (valid) + dump_entry(&cur, pos); + } + + return 0; +} + +static bool +valid_entry(union timestamp_entry_storage *u, off_t pos) +{ + struct timestamp_entry *entry = (struct timestamp_entry *)u; + debug_decl(valid_entry, SUDOERS_DEBUG_UTIL) + + switch (entry->version) { + case 1: + if (entry->size != sizeof(struct timestamp_entry_v1)) { + printf("wrong sized v1 record @ %lld, got %hu, expected %zu\n", + (long long)pos, entry->size, sizeof(struct timestamp_entry_v1)); + debug_return_bool(false); + } + break; + case 2: + if (entry->size != sizeof(struct timestamp_entry)) { + printf("wrong sized v2 record @ %lld, got %hu, expected %zu\n", + (long long)pos, entry->size, sizeof(struct timestamp_entry)); + debug_return_bool(false); + } + break; + default: + printf("unknown time stamp entry version %d @ %lld\n", + entry->version, (long long)pos); + debug_return_bool(false); + break; + } + debug_return_bool(true); +} + +static char * +type2string(int type) +{ + static char name[64]; + debug_decl(type2string, SUDOERS_DEBUG_UTIL) + + switch (type) { + case TS_LOCKEXCL: + debug_return_str("TS_LOCKEXCL"); + case TS_GLOBAL: + debug_return_str("TS_GLOBAL"); + case TS_TTY: + debug_return_str("TS_TTY"); + case TS_PPID: + debug_return_str("TS_PPID"); + } + snprintf(name, sizeof(name), "UNKNOWN (0x%x)", type); + debug_return_str(name); +} + +static void +print_flags(int flags) +{ + bool first = true; + debug_decl(print_flags, SUDOERS_DEBUG_UTIL) + + printf("flags: "); + if (ISSET(flags, TS_DISABLED)) { + printf("%sTS_DISABLED", first ? "" : ", "); + CLR(flags, TS_DISABLED); + first = false; + } + if (ISSET(flags, TS_ANYUID)) { + /* TS_ANYUID should never appear on disk. */ + printf("%sTS_ANYUID", first ? "" : ", "); + CLR(flags, TS_ANYUID); + first = false; + } + if (flags != 0) + printf("%s0x%x", first ? "" : ", ", flags); + putchar('\n'); + + debug_return; +} + +/* + * Convert an older entry to current. + */ +static bool +convert_entry(union timestamp_entry_storage *record) +{ + union timestamp_entry_storage orig; + debug_decl(convert_entry, SUDOERS_DEBUG_UTIL) + + if (record->common.version != 1) { + sudo_warnx("unexpected record version %hu", record->common.version); + debug_return_bool(false); + } + + /* The first four fields are the same regardless of version. */ + memcpy(&orig, record, sizeof(union timestamp_entry_storage)); + record->v2.auth_uid = orig.v1.auth_uid; + record->v2.sid = orig.v1.sid; + sudo_timespecclear(&record->v2.start_time); + record->v2.ts = orig.v1.ts; + if (record->common.type == TS_TTY) + record->v2.u.ttydev = orig.v1.u.ttydev; + else if (record->common.type == TS_PPID) + record->v2.u.ppid = orig.v1.u.ppid; + else + memset(&record->v2.u, 0, sizeof(record->v2.u)); + + debug_return_bool(true); +} + +static void +dump_entry(union timestamp_entry_storage *u, off_t pos) +{ + struct timestamp_entry *entry = (struct timestamp_entry *)u; + debug_decl(dump_entry, SUDOERS_DEBUG_UTIL) + + /* Convert to latest version as needed. */ + if (u->common.version != TS_VERSION) { + if (!convert_entry(u)) + debug_return; + } + + printf("position: %lld\n", (long long)pos); + printf("version: %hu\n", entry->version); + printf("size: %hu\n", entry->size); + printf("type: %s\n", type2string(entry->type)); + print_flags(entry->flags); + printf("auth uid: %d\n", (int)entry->auth_uid); + printf("session ID: %d\n", (int)entry->sid); + if (sudo_timespecisset(&entry->start_time)) + printf("start time: %s", ctime(&entry->start_time.tv_sec)); + if (sudo_timespecisset(&entry->ts)) + printf("time stamp: %s", ctime(&entry->ts.tv_sec)); + if (entry->type == TS_TTY) { + char tty[PATH_MAX]; + if (sudo_ttyname_dev(entry->u.ttydev, tty, sizeof(tty)) == NULL) + printf("terminal: %d\n", (int)entry->u.ttydev); + else + printf("terminal: %s\n", tty); + } else if (entry->type == TS_PPID) { + printf("parent pid: %d\n", (int)entry->u.ppid); + } + printf("\n"); + + debug_return; +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-f timestamp_file] | [-u username]\n", + getprogname()); + exit(1); +} diff --git a/src/ttyname.c b/src/ttyname.c index 6dca7e25a..a87e6b6f5 100644 --- a/src/ttyname.c +++ b/src/ttyname.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2017 Todd C. Miller + * Copyright (c) 2012-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 @@ -43,10 +43,8 @@ #include #include #if defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) || defined (HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) || defined(HAVE_STRUCT_KINFO_PROC2_P_TDEV) -# include /* for makedev/major/minor */ # include #elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV) -# include /* for makedev/major/minor */ # include # include #endif @@ -56,7 +54,6 @@ # include #endif #ifdef HAVE_PSTAT_GETPROC -# include /* for makedev/major/minor */ # include #endif @@ -87,271 +84,6 @@ # define sudo_kp_namelen 4 #endif -#if defined(sudo_kp_tdev) -/* - * Like ttyname() but uses a dev_t instead of an open fd. - * Returns name on success and NULL on failure, setting errno. - * The BSD version uses devname(). - */ -static char * -sudo_ttyname_dev(dev_t tdev, char *name, size_t namelen) -{ - char *dev; - debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) - - /* Some versions of devname() return NULL on failure, others do not. */ - dev = devname(tdev, S_IFCHR); - if (dev != NULL && *dev != '?' && *dev != '#') { - if (strlcpy(name, _PATH_DEV, namelen) < namelen && - strlcat(name, dev, namelen) < namelen) - debug_return_str(name); - errno = ERANGE; - } else { - /* Not all versions of devname() set errno. */ - errno = ENOENT; - } - debug_return_str(NULL); -} -#elif defined(HAVE__TTYNAME_DEV) -extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen); - -/* - * Like ttyname() but uses a dev_t instead of an open fd. - * Returns name on success and NULL on failure, setting errno. - * This version is just a wrapper around _ttyname_dev(). - */ -static char * -sudo_ttyname_dev(dev_t tdev, char *name, size_t namelen) -{ - int serrno = errno; - debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) - - /* - * _ttyname_dev() sets errno to ERANGE if namelen is too small - * but does not modify it if tdev is not found. - */ - errno = ENOENT; - if (_ttyname_dev(tdev, name, namelen) == NULL) - debug_return_str(NULL); - errno = serrno; - - debug_return_str(name); -} -#elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || defined(HAVE_PSTAT_GETPROC) || defined(__linux__) -/* - * Device nodes to ignore. - */ -static const char *ignore_devs[] = { - _PATH_DEV "stdin", - _PATH_DEV "stdout", - _PATH_DEV "stderr", - NULL -}; - -/* - * Do a scan of a directory looking for the specified device. - * Does not descend into subdirectories. - * Returns name on success and NULL on failure, setting errno. - */ -static char * -sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen) -{ - size_t sdlen; - char pathbuf[PATH_MAX]; - char *ret = NULL; - struct dirent *dp; - struct stat sb; - unsigned int i; - DIR *d = NULL; - debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL) - - if (dir[0] == '\0') { - errno = ENOENT; - goto done; - } - if ((d = opendir(dir)) == NULL) - goto done; - - if (fstat(dirfd(d), &sb) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to fstat %s", dir); - goto done; - } - if ((sb.st_mode & S_IWOTH) != 0) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "ignoring world-writable directory %s", dir); - errno = ENOENT; - goto done; - } - - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "scanning for dev %u in %s", (unsigned int)rdev, dir); - - sdlen = strlen(dir); - while (sdlen > 0 && dir[sdlen - 1] == '/') - sdlen--; - if (sdlen + 1 >= sizeof(pathbuf)) { - errno = ERANGE; - goto done; - } - memcpy(pathbuf, dir, sdlen); - pathbuf[sdlen++] = '/'; - - while ((dp = readdir(d)) != NULL) { - struct stat sb; - - /* Skip anything starting with "." */ - if (dp->d_name[0] == '.') - continue; - - pathbuf[sdlen] = '\0'; - if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name); - continue; - } - - /* Ignore device nodes listed in ignore_devs[]. */ - for (i = 0; ignore_devs[i] != NULL; i++) { - if (strcmp(pathbuf, ignore_devs[i]) == 0) - break; - } - if (ignore_devs[i] != NULL) { - sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, - "ignoring %s", pathbuf); - continue; - } - -# if defined(HAVE_STRUCT_DIRENT_D_TYPE) - /* - * Avoid excessive stat() calls by checking dp->d_type. - */ - switch (dp->d_type) { - case DT_CHR: - case DT_LNK: - case DT_UNKNOWN: - break; - default: - /* Not a character device or link, skip it. */ - sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, - "skipping non-device %s", pathbuf); - continue; - } -# endif - if (stat(pathbuf, &sb) == -1) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO, - "unable to stat %s", pathbuf); - continue; - } - if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "resolved dev %u as %s", (unsigned int)rdev, pathbuf); - if (strlcpy(name, pathbuf, namelen) < namelen) { - ret = name; - } else { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to store %s, have %zu, need %zu", - pathbuf, namelen, strlen(pathbuf) + 1); - errno = ERANGE; - } - goto done; - } - } - -done: - if (d != NULL) - closedir(d); - debug_return_str(ret); -} - -static char * -sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen) -{ - struct stat sb; - debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL) - - if (stat(devname, &sb) == 0) { - if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "comparing dev %u to %s: match!", - (unsigned int)rdev, devname); - if (strlcpy(buf, devname, buflen) < buflen) - debug_return_str(buf); - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "unable to store %s, have %zu, need %zu", - devname, buflen, strlen(devname) + 1); - errno = ERANGE; - } - } - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "comparing dev %u to %s: no", (unsigned int)rdev, devname); - debug_return_str(NULL); -} - -/* - * Like ttyname() but uses a dev_t instead of an open fd. - * Returns name on success and NULL on failure, setting errno. - * Generic version. - */ -static char * -sudo_ttyname_dev(dev_t rdev, char *buf, size_t buflen) -{ - const char *devsearch, *devsearch_end; - char path[PATH_MAX], *ret; - const char *cp, *ep; - size_t len; - debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL) - - /* - * First, check /dev/console. - */ - ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen); - if (ret != NULL) - goto done; - - /* - * Then check the device search path. - */ - devsearch = sudo_conf_devsearch_path(); - devsearch_end = devsearch + strlen(devsearch); - for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep); - cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) { - - len = (size_t)(ep - cp); - if (len >= sizeof(path)) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "devsearch entry %.*s too long", (int)len, cp); - continue; - } - memcpy(path, cp, len); - path[len] = '\0'; - - if (strcmp(path, _PATH_DEV "pts") == 0) { - /* Special case /dev/pts */ - len = (size_t)snprintf(path, sizeof(path), "%spts/%u", - _PATH_DEV, (unsigned int)minor(rdev)); - if (len >= sizeof(path)) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "devsearch entry %spts/%u too long", - _PATH_DEV, (unsigned int)minor(rdev)); - continue; - } - ret = sudo_dev_check(rdev, path, buf, buflen); - if (ret != NULL) - goto done; - } else { - /* Scan path, looking for rdev. */ - ret = sudo_ttyname_scan(path, rdev, buf, buflen); - if (ret != NULL || errno == ENOMEM) - goto done; - } - } - -done: - debug_return_str(ret); -} -#endif - #if defined(sudo_kp_tdev) /* * Store the name of the tty to which the process is attached in name. -- 2.40.0