From: Todd C. Miller Date: Thu, 27 Sep 2012 14:21:13 +0000 (-0400) Subject: Split off timestamp functions into their own source file. X-Git-Tag: SUDO_1_8_7~1^2~379 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0fa33ccf0f6e7fe85e74b099b440e3411c7cafea;p=sudo Split off timestamp functions into their own source file. --- diff --git a/MANIFEST b/MANIFEST index 2b2b5498a..c4faf74e2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -274,6 +274,8 @@ plugins/sudoers/sudoers2ldif plugins/sudoers/sudoers_version.h plugins/sudoers/sudoreplay.c plugins/sudoers/testsudoers.c +plugins/sudoers/timestamp.c +plugins/sudoers/timestamp.h plugins/sudoers/timestr.c plugins/sudoers/toke.c plugins/sudoers/toke.h diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index a7c90237b..42caa4f3a 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -130,7 +130,7 @@ LIBPARSESUDOERS_OBJS = alias.lo audit.lo defaults.lo gram.lo match.lo \ SUDOERS_OBJS = $(AUTH_OBJS) boottime.lo check.lo env.lo goodpath.lo \ group_plugin.lo find_path.lo interfaces.lo logging.lo \ logwrap.lo parse.lo set_perms.lo sudoers.lo sudo_nss.lo \ - iolog.lo iolog_path.lo @SUDOERS_OBJS@ + timestamp.lo iolog.lo iolog_path.lo @SUDOERS_OBJS@ VISUDO_OBJS = visudo.o goodpath.o find_path.o error.o @@ -447,7 +447,8 @@ check.lo: $(srcdir)/check.c $(top_builddir)/config.h $(srcdir)/sudoers.h \ $(incdir)/missing.h $(incdir)/error.h $(incdir)/alloc.h \ $(incdir)/list.h $(incdir)/fileops.h $(srcdir)/defaults.h \ $(devdir)/def_data.h $(srcdir)/logging.h $(srcdir)/sudo_nss.h \ - $(incdir)/sudo_plugin.h $(incdir)/sudo_debug.h $(incdir)/gettext.h + $(incdir)/sudo_plugin.h $(incdir)/sudo_debug.h $(incdir)/gettext.h \ + $(srcdir)/timestamp.h $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(DEFS) $(srcdir)/check.c check_addr.o: $(srcdir)/regress/parser/check_addr.c $(top_builddir)/config.h \ $(srcdir)/sudoers.h $(top_srcdir)/compat/stdbool.h \ @@ -789,6 +790,14 @@ testsudoers.o: $(srcdir)/testsudoers.c $(top_builddir)/config.h \ $(incdir)/sudo_conf.h $(incdir)/list.h $(incdir)/secure_path.h \ $(devdir)/gram.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(DEFS) $(srcdir)/testsudoers.c +timestamp.lo: $(srcdir)/timestamp.c $(top_builddir)/config.h \ + $(srcdir)/sudoers.h $(top_srcdir)/compat/stdbool.h \ + $(top_builddir)/pathnames.h $(incdir)/missing.h \ + $(incdir)/error.h $(incdir)/alloc.h $(incdir)/list.h \ + $(incdir)/fileops.h $(srcdir)/defaults.h $(devdir)/def_data.h \ + $(srcdir)/logging.h $(srcdir)/sudo_nss.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_debug.h $(incdir)/gettext.h $(srcdir)/timestamp.h + $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(DEFS) $(srcdir)/timestamp.c timestr.lo: $(srcdir)/timestr.c $(top_builddir)/config.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(DEFS) $(srcdir)/timestr.c toke.lo: $(devdir)/toke.c $(top_builddir)/config.h $(top_builddir)/config.h \ diff --git a/plugins/sudoers/check.c b/plugins/sudoers/check.c index 21cb2b196..15893984a 100644 --- a/plugins/sudoers/check.c +++ b/plugins/sudoers/check.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1993-1996,1998-2005, 2007-2011 + * Copyright (c) 1993-1996,1998-2005, 2007-2012 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any @@ -23,17 +23,6 @@ #include #include -#include -#include -#ifdef __linux__ -# include -#endif -#if defined(__sun) && defined(__SVR4) -# include -#endif -#ifndef __TANDEM -# include -#endif #include #ifdef STDC_HEADERS # include @@ -52,44 +41,16 @@ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ -#if TIME_WITH_SYS_TIME -# include -#endif #include #include -#include #include #include #include "sudoers.h" +#include "timestamp.h" -/* Status codes for timestamp_status() */ -#define TS_CURRENT 0 -#define TS_OLD 1 -#define TS_MISSING 2 -#define TS_NOFILE 3 -#define TS_ERROR 4 - -/* Flags for timestamp_status() */ -#define TS_MAKE_DIRS 1 -#define TS_REMOVE 2 - -/* - * Info stored in tty ticket from stat(2) to help with tty matching. - */ -static struct tty_info { - dev_t dev; /* ID of device tty resides on */ - dev_t rdev; /* tty device ID */ - ino_t ino; /* tty inode number */ - struct timeval ctime; /* tty inode change time */ -} tty_info; - -static int build_timestamp(char **, char **); -static int timestamp_status(char *, char *, char *, int); static char *expand_prompt(char *, char *, char *); -static void lecture(int); -static void update_timestamp(char *, char *); -static bool tty_is_devpts(const char *); +static bool display_lecture(int); static struct passwd *get_authpw(void); /* @@ -100,10 +61,7 @@ int check_user(int validated, int mode) { struct passwd *auth_pw; - char *timestampdir = NULL; - char *timestampfile = NULL; char *prompt; - struct stat sb; int status, rval = true; debug_decl(check_user, SUDO_DEBUG_AUTH) @@ -138,22 +96,12 @@ check_user(int validated, int mode) if (ISSET(mode, MODE_IGNORE_TICKET)) SET(validated, FLAG_CHECK_USER); - /* Stash the tty's ctime for tty ticket comparison. */ - if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) { - tty_info.dev = sb.st_dev; - tty_info.ino = sb.st_ino; - tty_info.rdev = sb.st_rdev; - if (tty_is_devpts(user_ttypath)) - ctim_get(&sb, &tty_info.ctime); - } - - if (build_timestamp(×tampdir, ×tampfile) == -1) { + if (build_timestamp() == -1) { rval = -1; goto done; } - status = timestamp_status(timestampdir, timestampfile, user_name, - TS_MAKE_DIRS); + status = timestamp_status(TS_MAKE_DIRS); if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) { /* Bail out if we are non-interactive and a password is required */ @@ -165,7 +113,7 @@ check_user(int validated, int mode) } /* XXX - should not lecture if askpass helper is being used. */ - lecture(status); + display_lecture(status); /* Expand any escapes in the prompt. */ prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt, @@ -176,9 +124,7 @@ check_user(int validated, int mode) /* Only update timestamp if user was validated. */ if (rval == true && ISSET(validated, VALIDATE_OK) && !ISSET(mode, MODE_IGNORE_TICKET) && status != TS_ERROR) - update_timestamp(timestampdir, timestampfile); - efree(timestampdir); - efree(timestampfile); + update_timestamp(); done: sudo_auth_cleanup(auth_pw); @@ -195,10 +141,11 @@ done: " #3) With great power comes great responsibility.\n\n" /* - * Standard sudo lecture. + * Display sudo lecture (standard or custom). + * Returns true if the user was lectured, else false. */ -static void -lecture(int status) +static bool +display_lecture(int status) { FILE *fp; char buf[BUFSIZ]; @@ -209,7 +156,7 @@ lecture(int status) if (def_lecture == never || (def_lecture == once && status != TS_MISSING && status != TS_ERROR)) - debug_return; + debug_return_int(false); memset(&msg, 0, sizeof(msg)); memset(&repl, 0, sizeof(repl)); @@ -227,49 +174,7 @@ lecture(int status) msg.msg = _(DEFAULT_LECTURE); sudo_conv(1, &msg, &repl); } - debug_return; -} - -/* - * Update the time on the timestamp file/dir or create it if necessary. - */ -static void -update_timestamp(char *timestampdir, char *timestampfile) -{ - debug_decl(update_timestamp, SUDO_DEBUG_AUTH) - - /* If using tty timestamps but we have no tty there is nothing to do. */ - if (def_tty_tickets && !user_ttypath) - debug_return; - - if (timestamp_uid != 0) - set_perms(PERM_TIMESTAMP); - if (timestampfile) { - /* - * Store tty info in timestamp file - */ - int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600); - if (fd == -1) - log_error(USE_ERRNO, _("unable to open %s"), timestampfile); - else { - lock_file(fd, SUDO_LOCK); - if (write(fd, &tty_info, sizeof(tty_info)) != sizeof(tty_info)) { - log_error(USE_ERRNO, _("unable to write to %s"), - timestampfile); - } - close(fd); - } - } else { - if (touch(-1, timestampdir, NULL) == -1) { - if (mkdir(timestampdir, 0700) == -1) { - log_error(USE_ERRNO, _("unable to mkdir %s"), - timestampdir); - } - } - } - if (timestamp_uid != 0) - restore_perms(); - debug_return; + debug_return_int(true); } /* @@ -414,335 +319,6 @@ user_is_exempt(void) debug_return_bool(rval); } -/* - * Fills in timestampdir as well as timestampfile if using tty tickets. - */ -static int -build_timestamp(char **timestampdir, char **timestampfile) -{ - char *dirparent; - int len; - debug_decl(build_timestamp, SUDO_DEBUG_AUTH) - - dirparent = def_timestampdir; - *timestampfile = NULL; - len = easprintf(timestampdir, "%s/%s", dirparent, user_name); - if (len >= PATH_MAX) - goto bad; - - /* - * Timestamp file may be a file in the directory or NUL to use - * the directory as the timestamp. - */ - if (def_tty_tickets) { - char *p; - - if ((p = strrchr(user_tty, '/'))) - p++; - else - p = user_tty; - if (def_targetpw) - len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name, - p, runas_pw->pw_name); - else - len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p); - if (len >= PATH_MAX) - goto bad; - } else if (def_targetpw) { - len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, - runas_pw->pw_name); - if (len >= PATH_MAX) - goto bad; - } else - *timestampfile = NULL; - - debug_return_int(len); -bad: - log_fatal(0, _("timestamp path too long: %s"), - *timestampfile ? *timestampfile : *timestampdir); - /* NOTREACHED */ - debug_return_int(-1); -} - -/* - * Check the timestamp file and directory and return their status. - */ -static int -timestamp_status(char *timestampdir, char *timestampfile, char *user, int flags) -{ - struct stat sb; - struct timeval boottime, mtime; - time_t now; - char *dirparent = def_timestampdir; - int status = TS_ERROR; /* assume the worst */ - debug_decl(timestamp_status, SUDO_DEBUG_AUTH) - - if (timestamp_uid != 0) - set_perms(PERM_TIMESTAMP); - - /* - * Sanity check dirparent and make it if it doesn't already exist. - * We start out assuming the worst (that the dir is not sane) and - * if it is ok upgrade the status to ``no timestamp file''. - * Note that we don't check the parent(s) of dirparent for - * sanity since the sudo dir is often just located in /tmp. - */ - if (lstat(dirparent, &sb) == 0) { - if (!S_ISDIR(sb.st_mode)) - log_error(0, _("%s exists but is not a directory (0%o)"), - dirparent, (unsigned int) sb.st_mode); - else if (sb.st_uid != timestamp_uid) - log_error(0, _("%s owned by uid %u, should be uid %u"), - dirparent, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - else if ((sb.st_mode & 0000022)) - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0700"), - dirparent, (unsigned int) sb.st_mode); - else { - if ((sb.st_mode & 0000777) != 0700) - (void) chmod(dirparent, 0700); - status = TS_MISSING; - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), dirparent); - } else { - /* No dirparent, try to make one. */ - if (ISSET(flags, TS_MAKE_DIRS)) { - if (mkdir(dirparent, S_IRWXU)) - log_error(USE_ERRNO, _("unable to mkdir %s"), - dirparent); - else - status = TS_MISSING; - } - } - if (status == TS_ERROR) - goto done; - - /* - * Sanity check the user's ticket dir. We start by downgrading - * the status to TS_ERROR. If the ticket dir exists and is sane - * this will be upgraded to TS_OLD. If the dir does not exist, - * it will be upgraded to TS_MISSING. - */ - status = TS_ERROR; /* downgrade status again */ - if (lstat(timestampdir, &sb) == 0) { - if (!S_ISDIR(sb.st_mode)) { - if (S_ISREG(sb.st_mode)) { - /* convert from old style */ - if (unlink(timestampdir) == 0) - status = TS_MISSING; - } else - log_error(0, _("%s exists but is not a directory (0%o)"), - timestampdir, (unsigned int) sb.st_mode); - } else if (sb.st_uid != timestamp_uid) - log_error(0, _("%s owned by uid %u, should be uid %u"), - timestampdir, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - else if ((sb.st_mode & 0000022)) - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0700"), - timestampdir, (unsigned int) sb.st_mode); - else { - if ((sb.st_mode & 0000777) != 0700) - (void) chmod(timestampdir, 0700); - status = TS_OLD; /* do date check later */ - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), timestampdir); - } else - status = TS_MISSING; - - /* - * If there is no user ticket dir, AND we are in tty ticket mode, - * AND the TS_MAKE_DIRS flag is set, create the user ticket dir. - */ - if (status == TS_MISSING && timestampfile && ISSET(flags, TS_MAKE_DIRS)) { - if (mkdir(timestampdir, S_IRWXU) == -1) { - status = TS_ERROR; - log_error(USE_ERRNO, _("unable to mkdir %s"), timestampdir); - } - } - - /* - * Sanity check the tty ticket file if it exists. - */ - if (timestampfile && status != TS_ERROR) { - if (status != TS_MISSING) - status = TS_NOFILE; /* dir there, file missing */ - if (def_tty_tickets && !user_ttypath) - goto done; /* no tty, always prompt */ - if (lstat(timestampfile, &sb) == 0) { - if (!S_ISREG(sb.st_mode)) { - status = TS_ERROR; - log_error(0, _("%s exists but is not a regular file (0%o)"), - timestampfile, (unsigned int) sb.st_mode); - } else { - /* If bad uid or file mode, complain and kill the bogus file. */ - if (sb.st_uid != timestamp_uid) { - log_error(0, - _("%s owned by uid %u, should be uid %u"), - timestampfile, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - (void) unlink(timestampfile); - } else if ((sb.st_mode & 0000022)) { - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0600"), - timestampfile, (unsigned int) sb.st_mode); - (void) unlink(timestampfile); - } else { - /* If not mode 0600, fix it. */ - if ((sb.st_mode & 0000777) != 0600) - (void) chmod(timestampfile, 0600); - - /* - * Check for stored tty info. If the file is zero-sized - * it is an old-style timestamp with no tty info in it. - * If removing, we don't care about the contents. - * The actual mtime check is done later. - */ - if (ISSET(flags, TS_REMOVE)) { - status = TS_OLD; - } else if (sb.st_size != 0) { - struct tty_info info; - int fd = open(timestampfile, O_RDONLY, 0644); - if (fd != -1) { - if (read(fd, &info, sizeof(info)) == sizeof(info) && - memcmp(&info, &tty_info, sizeof(info)) == 0) { - status = TS_OLD; - } - close(fd); - } - } - } - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), timestampfile); - status = TS_ERROR; - } - } - - /* - * If the file/dir exists and we are not removing it, check its mtime. - */ - if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) { - mtim_get(&sb, &mtime); - /* Negative timeouts only expire manually (sudo -k). */ - if (def_timestamp_timeout < 0 && mtime.tv_sec != 0) - status = TS_CURRENT; - else { - now = time(NULL); - if (def_timestamp_timeout && - now - mtime.tv_sec < 60 * def_timestamp_timeout) { - /* - * Check for bogus time on the stampfile. The clock may - * have been set back or someone could be trying to spoof us. - */ - if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) { - time_t tv_sec = (time_t)mtime.tv_sec; - log_error(0, - _("timestamp too far in the future: %20.20s"), - 4 + ctime(&tv_sec)); - if (timestampfile) - (void) unlink(timestampfile); - else - (void) rmdir(timestampdir); - status = TS_MISSING; - } else if (get_boottime(&boottime) && timevalcmp(&mtime, &boottime, <)) { - status = TS_OLD; - } else { - status = TS_CURRENT; - } - } - } - } - -done: - if (timestamp_uid != 0) - restore_perms(); - debug_return_int(status); -} - -/* - * Remove the timestamp ticket file/dir. - */ -void -remove_timestamp(bool remove) -{ - struct timeval tv; - char *timestampdir, *timestampfile, *path; - int status; - debug_decl(remove_timestamp, SUDO_DEBUG_AUTH) - - if (build_timestamp(×tampdir, ×tampfile) == -1) - debug_return; - - status = timestamp_status(timestampdir, timestampfile, user_name, - TS_REMOVE); - if (status != TS_MISSING && status != TS_ERROR) { - path = timestampfile ? timestampfile : timestampdir; - if (remove) { - if (timestampfile) - status = unlink(timestampfile); - else - status = rmdir(timestampdir); - if (status == -1 && errno != ENOENT) { - log_error(0, - _("unable to remove %s (%s), will reset to the epoch"), - path, strerror(errno)); - remove = false; - } - } - if (!remove) { - timevalclear(&tv); - if (touch(-1, path, &tv) == -1 && errno != ENOENT) - error(1, _("unable to reset %s to the epoch"), path); - } - } - efree(timestampdir); - efree(timestampfile); - - debug_return; -} - -/* - * Returns true if tty lives on a devpts, /dev or /devices filesystem, else - * false. Unlike most filesystems, the ctime of devpts nodes is not updated - * when the device node is written to, only when the inode's status changes, - * typically via the chmod, chown, link, rename, or utimes system calls. - * Since the ctime is "stable" in this case, we can stash it the tty ticket - * file and use it to determine whether the tty ticket file is stale. - */ -static bool -tty_is_devpts(const char *tty) -{ - bool retval = false; -#ifdef __linux__ - struct statfs sfs; - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) - -#ifndef DEVPTS_SUPER_MAGIC -# define DEVPTS_SUPER_MAGIC 0x1cd1 -#endif - - if (statfs(tty, &sfs) == 0) { - if (sfs.f_type == DEVPTS_SUPER_MAGIC) - retval = true; - } -#elif defined(__sun) && defined(__SVR4) - struct statvfs sfs; - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) - - if (statvfs(tty, &sfs) == 0) { - if (strcmp(sfs.f_fstr, "dev") == 0 || strcmp(sfs.f_fstr, "devices") == 0) - retval = true; - } -#else - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) -#endif /* __linux__ */ - debug_return_bool(retval); -} - /* * Get passwd entry for the user we are going to authenticate as. * By default, this is the user invoking sudo. In the most common diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index 712566707..2f514c844 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -500,10 +500,21 @@ update_defaults(int what) rc = false; break; case DEFAULTS_USER: +#if 1 + if (ISSET(what, SETDEF_USER)) { + int m; + m = userlist_matches(sudo_user.pw, &def->binding); + if (m == ALLOW) { + if (!set_default(def->var, def->val, def->op)) + rc = false; + } + } +#else if (ISSET(what, SETDEF_USER) && userlist_matches(sudo_user.pw, &def->binding) == ALLOW && !set_default(def->var, def->val, def->op)) rc = false; +#endif break; case DEFAULTS_RUNAS: if (ISSET(what, SETDEF_RUNAS) && diff --git a/plugins/sudoers/timestamp.c b/plugins/sudoers/timestamp.c new file mode 100644 index 000000000..f26cee32f --- /dev/null +++ b/plugins/sudoers/timestamp.c @@ -0,0 +1,448 @@ +/* + * Copyright (c) 1993-1996,1998-2005, 2007-2012 + * 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. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +#include + +#include +#include +#include +#include +#ifdef __linux__ +# include +#endif +#if defined(__sun) && defined(__SVR4) +# include +#endif +#ifndef __TANDEM +# include +#endif +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#if TIME_WITH_SYS_TIME +# include +#endif +#include +#include +#include +#include +#include + +#include "sudoers.h" +#include "timestamp.h" + +static bool tty_is_devpts(const char *); + +static struct sudo_tty_info tty_info; +static char timestampdir[PATH_MAX]; +static char timestampfile[PATH_MAX]; + +/* + * Fills in timestampdir as well as timestampfile if using tty tickets. + */ +int +build_timestamp(void) +{ + char *dirparent; + struct stat sb; + int len; + debug_decl(build_timestamp, SUDO_DEBUG_AUTH) + + /* Stash the tty's ctime for tty ticket comparison. */ + if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) { + tty_info.dev = sb.st_dev; + tty_info.ino = sb.st_ino; + tty_info.rdev = sb.st_rdev; + if (tty_is_devpts(user_ttypath)) + ctim_get(&sb, &tty_info.ctime); + } + + dirparent = def_timestampdir; + timestampfile[0] = '\0'; + len = snprintf(timestampdir, sizeof(timestampdir), "%s/%s", dirparent, + user_name); + if (len <= 0 || len >= sizeof(timestampdir)) + goto bad; + + /* + * Timestamp file may be a file in the directory or NUL to use + * the directory as the timestamp. + */ + if (def_tty_tickets) { + char *p; + + if ((p = strrchr(user_tty, '/'))) + p++; + else + p = user_tty; + if (def_targetpw) + len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s:%s", + dirparent, user_name, p, runas_pw->pw_name); + else + len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s", + dirparent, user_name, p); + if (len <= 0 || len >= sizeof(timestampfile)) + goto bad; + } else if (def_targetpw) { + len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s", + dirparent, user_name, runas_pw->pw_name); + if (len <= 0 || len >= sizeof(timestampfile)) + goto bad; + } + + debug_return_int(len); +bad: + log_fatal(0, _("timestamp path too long: %s"), + *timestampfile ? timestampfile : timestampdir); + /* NOTREACHED */ + debug_return_int(-1); +} + +/* + * Update the time on the timestamp file/dir or create it if necessary. + */ +bool +update_timestamp(void) +{ + debug_decl(update_timestamp, SUDO_DEBUG_AUTH) + + /* If using tty timestamps but we have no tty there is nothing to do. */ + if (def_tty_tickets && !user_ttypath) + debug_return_bool(false); + + if (timestamp_uid != 0) + set_perms(PERM_TIMESTAMP); + if (*timestampfile) { + /* + * Store tty info in timestamp file + */ + int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600); + if (fd == -1) + log_error(USE_ERRNO, _("unable to open %s"), timestampfile); + else { + lock_file(fd, SUDO_LOCK); + if (write(fd, &tty_info, sizeof(tty_info)) != sizeof(tty_info)) + log_error(USE_ERRNO, _("unable to write to %s"), timestampfile); + close(fd); + } + } else { + if (touch(-1, timestampdir, NULL) == -1) { + if (mkdir(timestampdir, 0700) == -1) { + log_error(USE_ERRNO, _("unable to mkdir %s"), + timestampdir); + } + } + } + if (timestamp_uid != 0) + restore_perms(); + debug_return_bool(true); +} + +/* + * Check the timestamp file and directory and return their status. + */ +int +timestamp_status(int flags) +{ + struct stat sb; + struct timeval boottime, mtime; + time_t now; + char *dirparent = def_timestampdir; + int status = TS_ERROR; /* assume the worst */ + debug_decl(timestamp_status, SUDO_DEBUG_AUTH) + + if (timestamp_uid != 0) + set_perms(PERM_TIMESTAMP); + + /* + * Sanity check dirparent and make it if it doesn't already exist. + * We start out assuming the worst (that the dir is not sane) and + * if it is ok upgrade the status to ``no timestamp file''. + * Note that we don't check the parent(s) of dirparent for + * sanity since the sudo dir is often just located in /tmp. + */ + if (lstat(dirparent, &sb) == 0) { + if (!S_ISDIR(sb.st_mode)) + log_error(0, _("%s exists but is not a directory (0%o)"), + dirparent, (unsigned int) sb.st_mode); + else if (sb.st_uid != timestamp_uid) + log_error(0, _("%s owned by uid %u, should be uid %u"), + dirparent, (unsigned int) sb.st_uid, + (unsigned int) timestamp_uid); + else if ((sb.st_mode & 0000022)) + log_error(0, + _("%s writable by non-owner (0%o), should be mode 0700"), + dirparent, (unsigned int) sb.st_mode); + else { + if ((sb.st_mode & 0000777) != 0700) + (void) chmod(dirparent, 0700); + status = TS_MISSING; + } + } else if (errno != ENOENT) { + log_error(USE_ERRNO, _("unable to stat %s"), dirparent); + } else { + /* No dirparent, try to make one. */ + if (ISSET(flags, TS_MAKE_DIRS)) { + if (mkdir(dirparent, S_IRWXU)) + log_error(USE_ERRNO, _("unable to mkdir %s"), + dirparent); + else + status = TS_MISSING; + } + } + if (status == TS_ERROR) + goto done; + + /* + * Sanity check the user's ticket dir. We start by downgrading + * the status to TS_ERROR. If the ticket dir exists and is sane + * this will be upgraded to TS_OLD. If the dir does not exist, + * it will be upgraded to TS_MISSING. + */ + status = TS_ERROR; /* downgrade status again */ + if (lstat(timestampdir, &sb) == 0) { + if (!S_ISDIR(sb.st_mode)) { + if (S_ISREG(sb.st_mode)) { + /* convert from old style */ + if (unlink(timestampdir) == 0) + status = TS_MISSING; + } else + log_error(0, _("%s exists but is not a directory (0%o)"), + timestampdir, (unsigned int) sb.st_mode); + } else if (sb.st_uid != timestamp_uid) + log_error(0, _("%s owned by uid %u, should be uid %u"), + timestampdir, (unsigned int) sb.st_uid, + (unsigned int) timestamp_uid); + else if ((sb.st_mode & 0000022)) + log_error(0, + _("%s writable by non-owner (0%o), should be mode 0700"), + timestampdir, (unsigned int) sb.st_mode); + else { + if ((sb.st_mode & 0000777) != 0700) + (void) chmod(timestampdir, 0700); + status = TS_OLD; /* do date check later */ + } + } else if (errno != ENOENT) { + log_error(USE_ERRNO, _("unable to stat %s"), timestampdir); + } else + status = TS_MISSING; + + /* + * If there is no user ticket dir, AND we are in tty ticket mode, + * AND the TS_MAKE_DIRS flag is set, create the user ticket dir. + */ + if (status == TS_MISSING && *timestampfile && ISSET(flags, TS_MAKE_DIRS)) { + if (mkdir(timestampdir, S_IRWXU) == -1) { + status = TS_ERROR; + log_error(USE_ERRNO, _("unable to mkdir %s"), timestampdir); + } + } + + /* + * Sanity check the tty ticket file if it exists. + */ + if (*timestampfile && status != TS_ERROR) { + if (status != TS_MISSING) + status = TS_NOFILE; /* dir there, file missing */ + if (def_tty_tickets && !user_ttypath) + goto done; /* no tty, always prompt */ + if (lstat(timestampfile, &sb) == 0) { + if (!S_ISREG(sb.st_mode)) { + status = TS_ERROR; + log_error(0, _("%s exists but is not a regular file (0%o)"), + timestampfile, (unsigned int) sb.st_mode); + } else { + /* If bad uid or file mode, complain and kill the bogus file. */ + if (sb.st_uid != timestamp_uid) { + log_error(0, + _("%s owned by uid %u, should be uid %u"), + timestampfile, (unsigned int) sb.st_uid, + (unsigned int) timestamp_uid); + (void) unlink(timestampfile); + } else if ((sb.st_mode & 0000022)) { + log_error(0, + _("%s writable by non-owner (0%o), should be mode 0600"), + timestampfile, (unsigned int) sb.st_mode); + (void) unlink(timestampfile); + } else { + /* If not mode 0600, fix it. */ + if ((sb.st_mode & 0000777) != 0600) + (void) chmod(timestampfile, 0600); + + /* + * Check for stored tty info. If the file is zero-sized + * it is an old-style timestamp with no tty info in it. + * If removing, we don't care about the contents. + * The actual mtime check is done later. + */ + if (ISSET(flags, TS_REMOVE)) { + status = TS_OLD; + } else if (sb.st_size != 0) { + struct sudo_tty_info info; + int fd = open(timestampfile, O_RDONLY, 0644); + if (fd != -1) { + if (read(fd, &info, sizeof(info)) == sizeof(info) && + memcmp(&info, &tty_info, sizeof(info)) == 0) { + status = TS_OLD; + } + close(fd); + } + } + } + } + } else if (errno != ENOENT) { + log_error(USE_ERRNO, _("unable to stat %s"), timestampfile); + status = TS_ERROR; + } + } + + /* + * If the file/dir exists and we are not removing it, check its mtime. + */ + if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) { + mtim_get(&sb, &mtime); + /* Negative timeouts only expire manually (sudo -k). */ + if (def_timestamp_timeout < 0 && mtime.tv_sec != 0) + status = TS_CURRENT; + else { + now = time(NULL); + if (def_timestamp_timeout && + now - mtime.tv_sec < 60 * def_timestamp_timeout) { + /* + * Check for bogus time on the stampfile. The clock may + * have been set back or someone could be trying to spoof us. + */ + if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) { + time_t tv_sec = (time_t)mtime.tv_sec; + log_error(0, + _("timestamp too far in the future: %20.20s"), + 4 + ctime(&tv_sec)); + if (*timestampfile) + (void) unlink(timestampfile); + else + (void) rmdir(timestampdir); + status = TS_MISSING; + } else if (get_boottime(&boottime) && timevalcmp(&mtime, &boottime, <)) { + status = TS_OLD; + } else { + status = TS_CURRENT; + } + } + } + } + +done: + if (timestamp_uid != 0) + restore_perms(); + debug_return_int(status); +} + +/* + * Remove the timestamp ticket file/dir. + */ +void +remove_timestamp(bool remove) +{ + struct timeval tv; + char *path; + int status; + debug_decl(remove_timestamp, SUDO_DEBUG_AUTH) + + if (build_timestamp() == -1) + debug_return; + + status = timestamp_status(TS_REMOVE); + if (status != TS_MISSING && status != TS_ERROR) { + path = *timestampfile ? timestampfile : timestampdir; + if (remove) { + if (*timestampfile) + status = unlink(timestampfile); + else + status = rmdir(timestampdir); + if (status == -1 && errno != ENOENT) { + log_error(0, + _("unable to remove %s (%s), will reset to the epoch"), + path, strerror(errno)); + remove = false; + } + } + if (!remove) { + timevalclear(&tv); + if (touch(-1, path, &tv) == -1 && errno != ENOENT) + error(1, _("unable to reset %s to the epoch"), path); + } + } + + debug_return; +} + +/* + * Returns true if tty lives on a devpts, /dev or /devices filesystem, else + * false. Unlike most filesystems, the ctime of devpts nodes is not updated + * when the device node is written to, only when the inode's status changes, + * typically via the chmod, chown, link, rename, or utimes system calls. + * Since the ctime is "stable" in this case, we can stash it the tty ticket + * file and use it to determine whether the tty ticket file is stale. + */ +static bool +tty_is_devpts(const char *tty) +{ + bool retval = false; +#ifdef __linux__ + struct statfs sfs; + debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) + +#ifndef DEVPTS_SUPER_MAGIC +# define DEVPTS_SUPER_MAGIC 0x1cd1 +#endif + + if (statfs(tty, &sfs) == 0) { + if (sfs.f_type == DEVPTS_SUPER_MAGIC) + retval = true; + } +#elif defined(__sun) && defined(__SVR4) + struct statvfs sfs; + debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) + + if (statvfs(tty, &sfs) == 0) { + if (strcmp(sfs.f_fstr, "dev") == 0 || strcmp(sfs.f_fstr, "devices") == 0) + retval = true; + } +#else + debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) +#endif /* __linux__ */ + debug_return_bool(retval); +} diff --git a/plugins/sudoers/timestamp.h b/plugins/sudoers/timestamp.h new file mode 100644 index 000000000..a0e9ead2e --- /dev/null +++ b/plugins/sudoers/timestamp.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1993-1996,1998-2005, 2007-2012 + * 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. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +#ifndef _SUDO_TIMESTAMP_H +#define _SUDO_TIMESTAMP_H + +/* Status codes for timestamp_status() */ +#define TS_CURRENT 0 +#define TS_OLD 1 +#define TS_MISSING 2 +#define TS_NOFILE 3 +#define TS_ERROR 4 + +/* Flags for timestamp_status() */ +#define TS_MAKE_DIRS 1 +#define TS_REMOVE 2 + +/* + * Info stored in tty ticket from stat(2) to help with tty matching. + */ +struct sudo_tty_info { + dev_t dev; /* ID of device tty resides on */ + dev_t rdev; /* tty device ID */ + ino_t ino; /* tty inode number */ + struct timeval ctime; /* tty inode change time */ +}; + +bool update_timestamp(void); +int build_timestamp(void); +int timestamp_status(int); + +#endif /* _SUDO_TIMESTAMP_H */