|
version 1.1, 2013/07/22 00:51:38
|
version 1.1.1.3, 2014/06/15 16:12:54
|
|
Line 1
|
Line 1
|
| /* |
/* |
| * Copyright (c) 1993-1996,1998-2005, 2007-2013 | * Copyright (c) 2014 Todd C. Miller <Todd.Miller@courtesan.com> |
| * Todd C. Miller <Todd.Miller@courtesan.com> | |
| * |
* |
| * Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
Line 13
|
Line 12
|
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| * |
|
| * 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 <config.h> |
#include <config.h> |
|
Line 24
|
Line 19
|
| #include <sys/types.h> |
#include <sys/types.h> |
| #include <sys/time.h> |
#include <sys/time.h> |
| #include <sys/stat.h> |
#include <sys/stat.h> |
| #ifndef __TANDEM |
|
| # include <sys/file.h> |
|
| #endif |
|
| #include <stdio.h> |
#include <stdio.h> |
| #ifdef STDC_HEADERS |
#ifdef STDC_HEADERS |
| # include <stdlib.h> |
# include <stdlib.h> |
|
Line 45
|
Line 37
|
| #ifdef HAVE_UNISTD_H |
#ifdef HAVE_UNISTD_H |
| # include <unistd.h> |
# include <unistd.h> |
| #endif /* HAVE_UNISTD_H */ |
#endif /* HAVE_UNISTD_H */ |
| #if TIME_WITH_SYS_TIME | #ifdef TIME_WITH_SYS_TIME |
| # include <time.h> |
# include <time.h> |
| #endif |
#endif |
| |
#ifndef HAVE_STRUCT_TIMESPEC |
| |
# include "compat/timespec.h" |
| |
#endif |
| #include <errno.h> |
#include <errno.h> |
| #include <fcntl.h> |
#include <fcntl.h> |
| #include <signal.h> |
|
| #include <pwd.h> |
#include <pwd.h> |
| #include <grp.h> |
#include <grp.h> |
| |
|
| #include "sudoers.h" |
#include "sudoers.h" |
| |
#include "secure_path.h" |
| #include "check.h" |
#include "check.h" |
| |
|
| static struct sudo_tty_info tty_info; | /* On Linux, CLOCK_MONOTONIC does not run while suspended. */ |
| static char timestampdir[PATH_MAX]; | #if defined(CLOCK_BOOTTIME) |
| static char timestampfile[PATH_MAX]; | # define SUDO_CLOCK_MONOTONIC CLOCK_BOOTTIME |
| | #elif defined(CLOCK_MONOTONIC) |
| | # define SUDO_CLOCK_MONOTONIC CLOCK_MONOTONIC |
| | #else |
| | # define SUDO_CLOCK_MONOTONIC CLOCK_REALTIME |
| | #endif |
| |
|
| |
static char timestamp_file[PATH_MAX]; |
| |
static off_t timestamp_hint = (off_t)-1; |
| |
static struct timestamp_entry timestamp_key; |
| |
|
| /* |
/* |
| * Fills in timestampdir as well as timestampfile if using tty tickets. | * Returns true if entry matches key, else false. |
| */ |
*/ |
| int | static bool |
| build_timestamp(struct passwd *pw) | ts_match_record(struct timestamp_entry *key, struct timestamp_entry *entry) |
| { |
{ |
| char *dirparent; | debug_decl(ts_match_record, SUDO_DEBUG_AUTH) |
| struct stat sb; | |
| int len; | |
| debug_decl(build_timestamp, SUDO_DEBUG_AUTH) | |
| |
|
| /* Stash the tty's device, session ID and ctime for ticket comparison. */ | if (entry->version != key->version) |
| if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) { | debug_return_bool(false); |
| tty_info.dev = sb.st_dev; | if (!ISSET(key->flags, TS_ANYUID) && entry->auth_uid != key->auth_uid) |
| tty_info.ino = sb.st_ino; | debug_return_bool(false); |
| tty_info.rdev = sb.st_rdev; | if (entry->type != key->type) |
| tty_info.uid = sb.st_uid; | debug_return_bool(false); |
| tty_info.gid = sb.st_gid; | switch (entry->type) { |
| tty_info.sid = user_sid; | case TS_GLOBAL: |
| | /* no ppid or tty to match */ |
| | break; |
| | case TS_PPID: |
| | /* verify parent pid */ |
| | if (entry->u.ppid != key->u.ppid) |
| | debug_return_bool(false); |
| | break; |
| | case TS_TTY: |
| | if (entry->u.ttydev != key->u.ttydev) |
| | debug_return_bool(false); |
| | break; |
| | default: |
| | /* unknown record type, ignore it */ |
| | debug_return_bool(false); |
| } |
} |
| |
debug_return_bool(true); |
| |
} |
| |
|
| dirparent = def_timestampdir; | /* |
| timestampfile[0] = '\0'; | * Searches the time stamp file descriptor for a record that matches key. |
| len = snprintf(timestampdir, sizeof(timestampdir), "%s/%s", dirparent, | * On success, fills in entry with the matching record and returns true. |
| user_name); | * On failure, returns false. |
| if (len <= 0 || len >= sizeof(timestampdir)) | * |
| goto bad; | * Note that records are searched starting at the current file offset, |
| | * which may not be the beginning of the file. |
| | */ |
| | static bool |
| | ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entry) |
| | { |
| | struct timestamp_entry cur; |
| | debug_decl(ts_find_record, SUDO_DEBUG_AUTH) |
| |
|
| /* |
/* |
| * Timestamp file may be a file in the directory or NUL to use | * Look for a matching record. |
| * the directory as the timestamp. | * We don't match on the sid or actual time stamp. |
| */ |
*/ |
| if (def_tty_tickets) { | while (read(fd, &cur, sizeof(cur)) == sizeof(cur)) { |
| char *p; | if (cur.size != sizeof(cur)) { |
| | /* wrong size, seek to start of next record */ |
| | sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, |
| | "wrong sized record, got %hu, expected %zu", |
| | cur.size, sizeof(cur)); |
| | lseek(fd, (off_t)cur.size - (off_t)sizeof(cur), SEEK_CUR); |
| | if (cur.size == 0) |
| | break; /* size must be non-zero */ |
| | continue; |
| | } |
| | if (ts_match_record(key, &cur)) { |
| | memcpy(entry, &cur, sizeof(struct timestamp_entry)); |
| | debug_return_bool(true); |
| | } |
| | } |
| | debug_return_bool(false); |
| | } |
| |
|
| if ((p = strrchr(user_tty, '/'))) | /* |
| p++; | * Find matching record to update or append a new one. |
| else | * Returns true if the entry was written successfully, else false. |
| p = user_tty; | */ |
| if (def_targetpw) | static bool |
| len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s:%s", | ts_update_record(int fd, struct timestamp_entry *entry, off_t timestamp_hint) |
| dirparent, user_name, p, runas_pw->pw_name); | { |
| else | struct timestamp_entry cur; |
| len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s", | ssize_t nwritten; |
| dirparent, user_name, p); | off_t old_eof = (off_t)-1; |
| if (len <= 0 || len >= sizeof(timestampfile)) | debug_decl(ts_update_record, SUDO_DEBUG_AUTH) |
| goto bad; | |
| } else if (def_targetpw) { | /* First try the hint if one is given. */ |
| len = snprintf(timestampfile, sizeof(timestampfile), "%s/%s/%s", | if (timestamp_hint != (off_t)-1) { |
| dirparent, user_name, runas_pw->pw_name); | if (lseek(fd, timestamp_hint, SEEK_SET) != -1) { |
| if (len <= 0 || len >= sizeof(timestampfile)) | if (read(fd, &cur, sizeof(cur)) == sizeof(cur)) { |
| goto bad; | if (ts_match_record(entry, &cur)) { |
| | sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| | "found existing time stamp record using hint"); |
| | goto found_it; |
| | } |
| | } |
| | } |
| } |
} |
| sudo_debug_printf(SUDO_DEBUG_INFO, "using timestamp file %s", timestampfile); |
|
| |
|
| |
/* Search for matching record. */ |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"searching for time stamp record"); |
| |
lseek(fd, (off_t)0, SEEK_SET); |
| |
if (ts_find_record(fd, entry, &cur)) { |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"found existing time stamp record"); |
| |
found_it: |
| |
/* back up over old record */ |
| |
lseek(fd, (off_t)0 - (off_t)cur.size, SEEK_CUR); |
| |
} else { |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"appending new time stamp record"); |
| |
old_eof = lseek(fd, (off_t)0, SEEK_CUR); |
| |
} |
| |
|
| |
/* Overwrite existing record or append to end. */ |
| |
nwritten = write(fd, entry, sizeof(struct timestamp_entry)); |
| |
if ((size_t)nwritten == sizeof(struct timestamp_entry)) |
| |
debug_return_bool(true); |
| |
|
| |
log_warning(nwritten == -1 ? USE_ERRNO : 0, |
| |
N_("unable to write to %s"), timestamp_file); |
| |
|
| |
/* Truncate on partial write to be safe. */ |
| |
if (nwritten > 0 && old_eof != (off_t)-1) { |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"short write, truncating partial time stamp record"); |
| |
if (ftruncate(fd, old_eof) != 0) { |
| |
warning(U_("unable to truncate time stamp file to %lld bytes"), |
| |
(long long)old_eof); |
| |
} |
| |
} |
| |
|
| |
debug_return_bool(false); |
| |
} |
| |
|
| |
/* |
| |
* Create a directory and any missing parent directories with the |
| |
* specified mode. |
| |
* Returns true on success. |
| |
* Returns false on failure and displays a warning to stderr. |
| |
*/ |
| |
static bool |
| |
ts_mkdirs(char *path, uid_t owner, mode_t mode, mode_t parent_mode, bool quiet) |
| |
{ |
| |
struct stat sb; |
| |
gid_t parent_gid = 0; |
| |
char *slash = path; |
| |
bool rval = false; |
| |
debug_decl(ts_mkdirs, SUDO_DEBUG_AUTH) |
| |
|
| |
while ((slash = strchr(slash + 1, '/')) != NULL) { |
| |
*slash = '\0'; |
| |
if (stat(path, &sb) != 0) { |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"mkdir %s, mode 0%o", path, parent_mode); |
| |
if (mkdir(path, parent_mode) != 0) { |
| |
if (!quiet) |
| |
warning(U_("unable to mkdir %s"), path); |
| |
goto done; |
| |
} |
| |
ignore_result(chown(path, (uid_t)-1, parent_gid)); |
| |
} else if (!S_ISDIR(sb.st_mode)) { |
| |
if (!quiet) { |
| |
warningx(U_("%s exists but is not a directory (0%o)"), |
| |
path, (unsigned int) sb.st_mode); |
| |
} |
| |
goto done; |
| |
} else { |
| |
/* Inherit gid of parent dir for ownership. */ |
| |
parent_gid = sb.st_gid; |
| |
} |
| |
*slash = '/'; |
| |
} |
| |
/* Create final path component. */ |
| |
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, |
| |
"mkdir %s, mode 0%o", path, mode); |
| |
if (mkdir(path, mode) != 0 && errno != EEXIST) { |
| |
if (!quiet) |
| |
warning(U_("unable to mkdir %s"), path); |
| |
goto done; |
| |
} |
| |
ignore_result(chown(path, owner, parent_gid)); |
| |
rval = true; |
| |
done: |
| |
debug_return_bool(rval); |
| |
} |
| |
|
| |
/* |
| |
* Check that path is owned by timestamp_uid and not writable by |
| |
* group or other. If path is missing and make_it is true, create |
| |
* the directory and its parent dirs. |
| |
* Returns true on success or false on failure, setting errno. |
| |
*/ |
| |
static bool |
| |
ts_secure_dir(char *path, bool make_it, bool quiet) |
| |
{ |
| |
struct stat sb; |
| |
bool rval = false; |
| |
debug_decl(ts_secure_dir, SUDO_DEBUG_AUTH) |
| |
|
| |
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "checking %s", path); |
| |
switch (sudo_secure_dir(path, timestamp_uid, -1, &sb)) { |
| |
case SUDO_PATH_SECURE: |
| |
rval = true; |
| |
break; |
| |
case SUDO_PATH_MISSING: |
| |
if (make_it && ts_mkdirs(path, timestamp_uid, 0700, 0711, quiet)) { |
| |
rval = true; |
| |
break; |
| |
} |
| |
errno = ENOENT; |
| |
break; |
| |
case SUDO_PATH_BAD_TYPE: |
| |
errno = ENOTDIR; |
| |
if (!quiet) |
| |
warning("%s", path); |
| |
break; |
| |
case SUDO_PATH_WRONG_OWNER: |
| |
if (!quiet) { |
| |
warningx(U_("%s is owned by uid %u, should be %u"), |
| |
path, (unsigned int) sb.st_uid, |
| |
(unsigned int) timestamp_uid); |
| |
} |
| |
errno = EACCES; |
| |
break; |
| |
case SUDO_PATH_GROUP_WRITABLE: |
| |
if (!quiet) |
| |
warningx(U_("%s is group writable"), path); |
| |
errno = EACCES; |
| |
break; |
| |
} |
| |
debug_return_bool(rval); |
| |
} |
| |
|
| |
/* |
| |
* Fills in the timestamp_file[] global variable. |
| |
* Returns the length of timestamp_file. |
| |
*/ |
| |
int |
| |
build_timestamp(struct passwd *pw) |
| |
{ |
| |
int len; |
| |
debug_decl(build_timestamp, SUDO_DEBUG_AUTH) |
| |
|
| |
len = snprintf(timestamp_file, sizeof(timestamp_file), "%s/%s", |
| |
def_timestampdir, user_name); |
| |
if (len <= 0 || (size_t)len >= sizeof(timestamp_file)) { |
| |
log_fatal(0, N_("timestamp path too long: %s/%s"), |
| |
def_timestampdir, user_name); |
| |
} |
| |
|
| debug_return_int(len); |
debug_return_int(len); |
| bad: |
|
| log_fatal(0, N_("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. |
* Update the time on the timestamp file/dir or create it if necessary. |
| |
* Returns true on success or false on failure. |
| */ |
*/ |
| bool |
bool |
| update_timestamp(struct passwd *pw) |
update_timestamp(struct passwd *pw) |
| { |
{ |
| |
struct timestamp_entry entry; |
| |
bool rval = false; |
| |
int fd; |
| debug_decl(update_timestamp, SUDO_DEBUG_AUTH) |
debug_decl(update_timestamp, SUDO_DEBUG_AUTH) |
| |
|
| /* If using tty timestamps but we have no tty there is nothing to do. */ | /* Zero timeout means don't update the time stamp file. */ |
| if (def_tty_tickets && !user_ttypath) | if (def_timestamp_timeout == 0) |
| debug_return_bool(false); | goto done; |
| |
|
| |
/* Check/create parent directories as needed. */ |
| |
if (!ts_secure_dir(def_timestampdir, true, false)) |
| |
goto done; |
| |
|
| |
/* Fill in time stamp. */ |
| |
memcpy(&entry, ×tamp_key, sizeof(struct timestamp_entry)); |
| |
clock_gettime(SUDO_CLOCK_MONOTONIC, &entry.ts); |
| |
|
| |
/* Open time stamp file and lock it for exclusive access. */ |
| if (timestamp_uid != 0) |
if (timestamp_uid != 0) |
| set_perms(PERM_TIMESTAMP); |
set_perms(PERM_TIMESTAMP); |
| if (*timestampfile) { | fd = open(timestamp_file, O_RDWR|O_CREAT, 0600); |
| /* | |
| * Store tty info in timestamp file | |
| */ | |
| int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600); | |
| if (fd == -1) | |
| log_warning(USE_ERRNO, N_("unable to open %s"), timestampfile); | |
| else { | |
| lock_file(fd, SUDO_LOCK); | |
| if (write(fd, &tty_info, sizeof(tty_info)) != sizeof(tty_info)) | |
| log_warning(USE_ERRNO, N_("unable to write to %s"), timestampfile); | |
| close(fd); | |
| } | |
| } else { | |
| if (touch(-1, timestampdir, NULL) == -1) { | |
| if (mkdir(timestampdir, 0700) == -1) { | |
| log_warning(USE_ERRNO, N_("unable to mkdir %s"), | |
| timestampdir); | |
| } | |
| } | |
| } | |
| if (timestamp_uid != 0) |
if (timestamp_uid != 0) |
| restore_perms(); |
restore_perms(); |
| debug_return_bool(true); | if (fd == -1) { |
| | log_warning(USE_ERRNO, N_("unable to open %s"), timestamp_file); |
| | goto done; |
| | } |
| | |
| | /* Update record or append a new one. */ |
| | lock_file(fd, SUDO_LOCK); |
| | ts_update_record(fd, &entry, timestamp_hint); |
| | close(fd); |
| | |
| | rval = true; |
| | |
| | done: |
| | debug_return_bool(rval); |
| } |
} |
| |
|
| /* |
/* |
| * Check the timestamp file and directory and return their status. |
* Check the timestamp file and directory and return their status. |
| |
* Returns one of TS_CURRENT, TS_OLD, TS_MISSING, TS_NOFILE, TS_ERROR. |
| */ |
*/ |
| static int | int |
| timestamp_status_internal(bool removing) | timestamp_status(struct passwd *pw) |
| { |
{ |
| struct stat sb; | struct timestamp_entry entry; |
| struct timeval boottime, mtime; | struct timespec diff, timeout; |
| time_t now; | |
| char *dirparent = def_timestampdir; | |
| int status = TS_ERROR; /* assume the worst */ |
int status = TS_ERROR; /* assume the worst */ |
| debug_decl(timestamp_status_internal, SUDO_DEBUG_AUTH) | struct stat sb; |
| | int fd = -1; |
| | debug_decl(timestamp_status, SUDO_DEBUG_AUTH) |
| |
|
| if (timestamp_uid != 0) | /* Reset time stamp offset hint. */ |
| set_perms(PERM_TIMESTAMP); | timestamp_hint = (off_t)-1; |
| |
|
| |
/* Zero timeout means ignore time stamp files. */ |
| |
if (def_timestamp_timeout == 0) { |
| |
status = TS_OLD; /* XXX - could also be TS_MISSING */ |
| |
goto done; |
| |
} |
| |
|
| |
/* Ignore time stamp files in an insecure directory. */ |
| |
if (!ts_secure_dir(def_timestampdir, false, false)) { |
| |
if (errno != ENOENT) { |
| |
status = TS_ERROR; |
| |
goto done; |
| |
} |
| |
status = TS_MISSING; /* not insecure, just missing */ |
| |
} |
| |
|
| /* |
/* |
| * Sanity check dirparent and make it if it doesn't already exist. | * Create a key used for matching entries in the time stamp file. |
| * We start out assuming the worst (that the dir is not sane) and | * The actual time stamp in the key is used below as the time "now". |
| * 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) { | memset(×tamp_key, 0, sizeof(timestamp_key)); |
| if (!S_ISDIR(sb.st_mode)) | timestamp_key.version = TS_VERSION; |
| log_warning(0, N_("%s exists but is not a directory (0%o)"), | timestamp_key.size = sizeof(timestamp_key); |
| dirparent, (unsigned int) sb.st_mode); | timestamp_key.type = TS_GLOBAL; /* may be overriden below */ |
| else if (sb.st_uid != timestamp_uid) | if (pw != NULL) { |
| log_warning(0, N_("%s owned by uid %u, should be uid %u"), | timestamp_key.auth_uid = pw->pw_uid; |
| dirparent, (unsigned int) sb.st_uid, | |
| (unsigned int) timestamp_uid); | |
| else if ((sb.st_mode & 0000022)) | |
| log_warning(0, | |
| N_("%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_warning(USE_ERRNO, N_("unable to stat %s"), dirparent); | |
| } else { |
} else { |
| /* No dirparent, try to make one. */ | timestamp_key.flags = TS_ANYUID; |
| if (!removing) { | } |
| if (mkdir(dirparent, S_IRWXU)) | timestamp_key.sid = user_sid; |
| log_warning(USE_ERRNO, N_("unable to mkdir %s"), | if (def_tty_tickets) { |
| dirparent); | if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) { |
| else | /* tty-based time stamp */ |
| status = TS_MISSING; | timestamp_key.type = TS_TTY; |
| | timestamp_key.u.ttydev = sb.st_rdev; |
| | } else { |
| | /* ppid-based time stamp */ |
| | timestamp_key.type = TS_PPID; |
| | timestamp_key.u.ppid = getppid(); |
| } |
} |
| } |
} |
| if (status == TS_ERROR) | clock_gettime(SUDO_CLOCK_MONOTONIC, ×tamp_key.ts); |
| | |
| | /* If the time stamp dir is missing there is nothing to do. */ |
| | if (status == TS_MISSING) |
| goto done; |
goto done; |
| |
|
| /* | /* Open time stamp file and lock it for exclusive access. */ |
| * Sanity check the user's ticket dir. We start by downgrading | if (timestamp_uid != 0) |
| * the status to TS_ERROR. If the ticket dir exists and is sane | set_perms(PERM_TIMESTAMP); |
| * this will be upgraded to TS_OLD. If the dir does not exist, | fd = open(timestamp_file, O_RDWR); |
| * it will be upgraded to TS_MISSING. | if (timestamp_uid != 0) |
| */ | restore_perms(); |
| status = TS_ERROR; /* downgrade status again */ | if (fd == -1) { |
| 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_warning(0, N_("%s exists but is not a directory (0%o)"), | |
| timestampdir, (unsigned int) sb.st_mode); | |
| } else if (sb.st_uid != timestamp_uid) | |
| log_warning(0, N_("%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_warning(0, | |
| N_("%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_warning(USE_ERRNO, N_("unable to stat %s"), timestampdir); | |
| } else | |
| status = TS_MISSING; |
status = TS_MISSING; |
| |
goto done; |
| |
} |
| |
lock_file(fd, SUDO_LOCK); |
| |
|
| /* | /* Ignore and clear time stamp file if mtime predates boot time. */ |
| * If there is no user ticket dir, AND we are in tty ticket mode, | if (fstat(fd, &sb) == 0) { |
| * AND we are not just going to remove it, create the user ticket dir. | struct timeval boottime, mtime; |
| */ | |
| if (status == TS_MISSING && *timestampfile && !removing) { | mtim_get(&sb, &mtime); |
| if (mkdir(timestampdir, S_IRWXU) == -1) { | if (get_boottime(&boottime) && sudo_timevalcmp(&mtime, &boottime, <)) { |
| status = TS_ERROR; | ignore_result(ftruncate(fd, (off_t)0)); |
| log_warning(USE_ERRNO, N_("unable to mkdir %s"), timestampdir); | status = TS_MISSING; |
| | goto done; |
| } |
} |
| } |
} |
| |
|
| /* | /* Read existing record, if any. */ |
| * Sanity check the tty ticket file if it exists. | if (!ts_find_record(fd, ×tamp_key, &entry)) { |
| */ | status = TS_MISSING; |
| if (*timestampfile && status != TS_ERROR) { | goto done; |
| 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_warning(0, N_("%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_warning(0, | |
| N_("%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_warning(0, | |
| N_("%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); | |
| |
|
| /* | /* Set record position hint for use by update_timestamp() */ |
| * Check for stored tty info. If the file is zero-sized | timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR); |
| * it is an old-style timestamp with no tty info in it. | if (timestamp_hint != (off_t)-1) |
| * If removing, we don't care about the contents. | timestamp_hint -= entry.size; |
| * The actual mtime check is done later. | |
| */ | if (ISSET(entry.flags, TS_DISABLED)) { |
| if (removing) { | status = TS_OLD; /* disabled via sudo -k */ |
| status = TS_OLD; | goto done; |
| } 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_warning(USE_ERRNO, N_("unable to stat %s"), timestampfile); | |
| status = TS_ERROR; | |
| } | |
| } |
} |
| |
|
| /* | if (entry.type != TS_GLOBAL && entry.sid != timestamp_key.sid) { |
| * If the file/dir exists and we are not removing it, check its mtime. | status = TS_OLD; /* belongs to different session */ |
| */ | goto done; |
| if (status == TS_OLD && !removing) { | } |
| mtim_get(&sb, &mtime); | |
| if (timevalisset(&mtime)) { | /* Negative timeouts only expire manually (sudo -k). */ |
| /* Negative timeouts only expire manually (sudo -k). */ | if (def_timestamp_timeout < 0) { |
| if (def_timestamp_timeout < 0) { | status = TS_CURRENT; |
| status = TS_CURRENT; | goto done; |
| } else { | } |
| time(&now); | |
| if (def_timestamp_timeout && | /* Compare stored time stamp with current time. */ |
| now - mtime.tv_sec < 60 * def_timestamp_timeout) { | sudo_timespecsub(×tamp_key.ts, &entry.ts, &diff); |
| /* | timeout.tv_sec = 60 * def_timestamp_timeout; |
| * Check for bogus time on the stampfile. The clock may | timeout.tv_nsec = ((60.0 * def_timestamp_timeout) - (double)timeout.tv_sec) |
| * have been set back or user could be trying to spoof us. | * 1000000000.0; |
| */ | if (sudo_timespeccmp(&diff, &timeout, <)) { |
| if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) { | status = TS_CURRENT; |
| time_t tv_sec = (time_t)mtime.tv_sec; | #ifdef CLOCK_MONOTONIC |
| log_warning(0, | /* A monotonic clock should never run backwards. */ |
| N_("timestamp too far in the future: %20.20s"), | if (diff.tv_sec < 0) { |
| 4 + ctime(&tv_sec)); | log_warning(0, N_("ignoring time stamp from the future")); |
| if (*timestampfile) | status = TS_OLD; |
| (void) unlink(timestampfile); | SET(entry.flags, TS_DISABLED); |
| else | ts_update_record(fd, &entry, timestamp_hint); |
| (void) rmdir(timestampdir); | |
| status = TS_MISSING; | |
| } else if (get_boottime(&boottime) && | |
| timevalcmp(&mtime, &boottime, <)) { | |
| status = TS_OLD; | |
| } else { | |
| status = TS_CURRENT; | |
| } | |
| } | |
| } | |
| } |
} |
| |
#else |
| |
/* Check for bogus (future) time in the stampfile. */ |
| |
sudo_timespecsub(&entry.ts, ×tamp_key.ts, &diff); |
| |
timeout.tv_sec *= 2; |
| |
if (sudo_timespeccmp(&diff, &timeout, >)) { |
| |
time_t tv_sec = (time_t)entry.ts.tv_sec; |
| |
log_warning(0, |
| |
N_("time stamp too far in the future: %20.20s"), |
| |
4 + ctime(&tv_sec)); |
| |
status = TS_OLD; |
| |
SET(entry.flags, TS_DISABLED); |
| |
ts_update_record(fd, &entry, timestamp_hint); |
| |
} |
| |
#endif /* CLOCK_MONOTONIC */ |
| |
} else { |
| |
status = TS_OLD; |
| } |
} |
| |
|
| done: |
done: |
| if (timestamp_uid != 0) | if (fd != -1) |
| restore_perms(); | close(fd); |
| debug_return_int(status); |
debug_return_int(status); |
| } |
} |
| |
|
| int |
|
| timestamp_status(struct passwd *pw) |
|
| { |
|
| return timestamp_status_internal(false); |
|
| } |
|
| |
|
| /* |
/* |
| * Remove the timestamp ticket file/dir. | * Remove the timestamp entry or file if unlink_it is set. |
| */ |
*/ |
| void |
void |
| remove_timestamp(bool remove) | remove_timestamp(bool unlink_it) |
| { |
{ |
| struct timeval tv; | struct timestamp_entry entry; |
| char *path; | int fd = -1; |
| int status; | |
| debug_decl(remove_timestamp, SUDO_DEBUG_AUTH) |
debug_decl(remove_timestamp, SUDO_DEBUG_AUTH) |
| |
|
| if (build_timestamp(NULL) == -1) |
if (build_timestamp(NULL) == -1) |
| debug_return; |
debug_return; |
| |
|
| status = timestamp_status_internal(true); | /* For "sudo -K" simply unlink the time stamp file. */ |
| if (status != TS_MISSING && status != TS_ERROR) { | if (unlink_it) { |
| path = *timestampfile ? timestampfile : timestampdir; | (void) unlink(timestamp_file); |
| if (remove) { | debug_return; |
| if (*timestampfile) | } |
| status = unlink(timestampfile); | |
| else | /* |
| status = rmdir(timestampdir); | * Create a key used for matching entries in the time stamp file. |
| if (status == -1 && errno != ENOENT) { | */ |
| log_warning(0, | memset(×tamp_key, 0, sizeof(timestamp_key)); |
| N_("unable to remove %s, will reset to the epoch"), path); | timestamp_key.version = TS_VERSION; |
| remove = false; | timestamp_key.size = sizeof(timestamp_key); |
| } | timestamp_key.type = TS_GLOBAL; /* may be overriden below */ |
| | timestamp_key.flags = TS_ANYUID; |
| | if (def_tty_tickets) { |
| | struct stat sb; |
| | if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) { |
| | /* tty-based time stamp */ |
| | timestamp_key.type = TS_TTY; |
| | timestamp_key.u.ttydev = sb.st_rdev; |
| | } else { |
| | /* ppid-based time stamp */ |
| | timestamp_key.type = TS_PPID; |
| | timestamp_key.u.ppid = getppid(); |
| } |
} |
| if (!remove) { |
|
| timevalclear(&tv); |
|
| if (touch(-1, path, &tv) == -1 && errno != ENOENT) |
|
| fatal(_("unable to reset %s to the epoch"), path); |
|
| } |
|
| } |
} |
| |
|
| |
/* Open time stamp file and lock it for exclusive access. */ |
| |
if (timestamp_uid != 0) |
| |
set_perms(PERM_TIMESTAMP); |
| |
fd = open(timestamp_file, O_RDWR); |
| |
if (timestamp_uid != 0) |
| |
restore_perms(); |
| |
if (fd == -1) |
| |
goto done; |
| |
lock_file(fd, SUDO_LOCK); |
| |
|
| |
/* |
| |
* Find matching entries and invalidate them. |
| |
*/ |
| |
while (ts_find_record(fd, ×tamp_key, &entry)) { |
| |
/* Set record position hint for use by update_timestamp() */ |
| |
timestamp_hint = lseek(fd, (off_t)0, SEEK_CUR); |
| |
if (timestamp_hint != (off_t)-1) |
| |
timestamp_hint -= (off_t)entry.size; |
| |
/* Disable the entry. */ |
| |
SET(entry.flags, TS_DISABLED); |
| |
ts_update_record(fd, &entry, timestamp_hint); |
| |
} |
| |
close(fd); |
| |
|
| |
done: |
| debug_return; |
debug_return; |
| } |
} |
| |
|
| /* |
/* |
| * Lecture status is currently implied by the timestamp status but | * Returns true if the user has already been lectured. |
| * may be stored separately in a future release. | |
| */ |
*/ |
| bool |
bool |
| |
already_lectured(int unused) |
| |
{ |
| |
char status_file[PATH_MAX]; |
| |
struct stat sb; |
| |
int len; |
| |
debug_decl(already_lectured, SUDO_DEBUG_AUTH) |
| |
|
| |
if (ts_secure_dir(def_lecture_status_dir, false, true)) { |
| |
len = snprintf(status_file, sizeof(status_file), "%s/%s", |
| |
def_lecture_status_dir, user_name); |
| |
if (len <= 0 || (size_t)len >= sizeof(status_file)) { |
| |
log_fatal(0, N_("lecture status path too long: %s/%s"), |
| |
def_lecture_status_dir, user_name); |
| |
} |
| |
debug_return_bool(stat(status_file, &sb) == 0); |
| |
} |
| |
debug_return_bool(false); |
| |
} |
| |
|
| |
/* |
| |
* Create the lecture status file. |
| |
* Returns true on success or false on failure. |
| |
*/ |
| |
bool |
| set_lectured(void) |
set_lectured(void) |
| { |
{ |
| return true; | char lecture_status[PATH_MAX]; |
| | int len, fd = -1; |
| | debug_decl(set_lectured, SUDO_DEBUG_AUTH) |
| | |
| | len = snprintf(lecture_status, sizeof(lecture_status), "%s/%s", |
| | def_lecture_status_dir, user_name); |
| | if (len <= 0 || (size_t)len >= sizeof(lecture_status)) { |
| | log_fatal(0, N_("lecture status path too long: %s/%s"), |
| | def_lecture_status_dir, user_name); |
| | } |
| | |
| | /* Sanity check lecture dir and create if missing. */ |
| | if (!ts_secure_dir(def_lecture_status_dir, true, false)) |
| | goto done; |
| | |
| | /* Create lecture file. */ |
| | if (timestamp_uid != 0) |
| | set_perms(PERM_TIMESTAMP); |
| | fd = open(lecture_status, O_WRONLY|O_CREAT|O_TRUNC, 0600); |
| | if (timestamp_uid != 0) |
| | restore_perms(); |
| | if (fd != -1) |
| | close(fd); |
| | |
| | done: |
| | debug_return_bool(fd != -1 ? true : false); |
| } |
} |