#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include "sudoers.h"
#include "check.h"
#define TIMESTAMP_OPEN_ERROR -1
#define TIMESTAMP_PERM_ERROR -2
struct ts_cookie {
char *fname;
int fd;
pid_t sid;
bool locked;
off_t pos;
struct timestamp_entry key;
};
static bool
ts_match_record(struct timestamp_entry *key, struct timestamp_entry *entry,
unsigned int recno)
{
debug_decl(ts_match_record, SUDOERS_DEBUG_AUTH)
if (entry->version != key->version) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u record version mismatch (want %u, got %u)", __func__, recno,
key->version, entry->version);
debug_return_bool(false);
}
if (!ISSET(key->flags, TS_ANYUID) && entry->auth_uid != key->auth_uid) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u record uid mismatch (want %u, got %u)", __func__, recno,
(unsigned int)key->auth_uid, (unsigned int)entry->auth_uid);
debug_return_bool(false);
}
if (entry->type != key->type) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u record type mismatch (want %u, got %u)", __func__, recno,
key->type, entry->type);
debug_return_bool(false);
}
switch (entry->type) {
case TS_GLOBAL:
break;
case TS_PPID:
if (entry->u.ppid != key->u.ppid) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u record ppid mismatch (want %d, got %d)", __func__, recno,
(int)key->u.ppid, (int)entry->u.ppid);
debug_return_bool(false);
}
if (sudo_timespeccmp(&entry->start_time, &key->start_time, !=)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u ppid start time mismatch", __func__, recno);
debug_return_bool(false);
}
break;
case TS_TTY:
if (entry->u.ttydev != key->u.ttydev) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u record tty mismatch (want 0x%x, got 0x%x)", __func__,
recno, (unsigned int)key->u.ttydev, (unsigned int)entry->u.ttydev);
debug_return_bool(false);
}
if (sudo_timespeccmp(&entry->start_time, &key->start_time, !=)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s:%u session leader start time mismatch", __func__, recno);
debug_return_bool(false);
}
break;
default:
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"%s:%u unknown time stamp record type %d", __func__, recno,
entry->type);
debug_return_bool(false);
}
debug_return_bool(true);
}
static bool
ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entry)
{
struct timestamp_entry cur;
unsigned int recno = 0;
debug_decl(ts_find_record, SUDOERS_DEBUG_AUTH)
while (read(fd, &cur, sizeof(cur)) == sizeof(cur)) {
recno++;
if (cur.size != sizeof(cur)) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"wrong sized record, got %hu, expected %zu",
cur.size, sizeof(cur));
if (lseek(fd, (off_t)cur.size - (off_t)sizeof(cur), SEEK_CUR) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek forward %d",
(int)cur.size - (int)sizeof(cur));
break;
}
if (cur.size == 0)
break;
continue;
}
if (ts_match_record(key, &cur, recno)) {
memcpy(entry, &cur, sizeof(struct timestamp_entry));
debug_return_bool(true);
}
}
debug_return_bool(false);
}
static bool
ts_mkdirs(char *path, uid_t owner, gid_t group, mode_t mode,
mode_t parent_mode, bool quiet)
{
bool ret;
mode_t omask;
debug_decl(ts_mkdirs, SUDOERS_DEBUG_AUTH)
omask = umask(ACCESSPERMS & ~(mode|parent_mode));
ret = sudo_mkdir_parents(path, owner, group, parent_mode, quiet);
if (ret) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode,
(int)owner, (int)group);
if (mkdir(path, mode) != 0 && errno != EEXIST) {
if (!quiet)
sudo_warn(U_("unable to mkdir %s"), path);
ret = false;
} else {
if (chown(path, owner, group) != 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to chown %d:%d %s", __func__,
(int)owner, (int)group, path);
}
}
}
umask(omask);
debug_return_bool(ret);
}
static bool
ts_secure_dir(char *path, bool make_it, bool quiet)
{
struct stat sb;
bool ret = false;
debug_decl(ts_secure_dir, SUDOERS_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:
ret = true;
break;
case SUDO_PATH_MISSING:
if (make_it && ts_mkdirs(path, timestamp_uid, timestamp_gid, S_IRWXU,
S_IRWXU|S_IXGRP|S_IXOTH, quiet)) {
ret = true;
break;
}
errno = ENOENT;
break;
case SUDO_PATH_BAD_TYPE:
errno = ENOTDIR;
if (!quiet)
sudo_warn("%s", path);
break;
case SUDO_PATH_WRONG_OWNER:
if (!quiet) {
sudo_warnx(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)
sudo_warnx(U_("%s is group writable"), path);
errno = EACCES;
break;
}
debug_return_bool(ret);
}
static int
ts_open(const char *path, int flags)
{
bool uid_changed = false;
int fd;
debug_decl(ts_open, SUDOERS_DEBUG_AUTH)
if (timestamp_uid != 0)
uid_changed = set_perms(PERM_TIMESTAMP);
fd = open(path, flags, S_IRUSR|S_IWUSR);
if (uid_changed && !restore_perms()) {
if (fd != -1) {
int serrno = errno;
close(fd);
errno = serrno;
fd = TIMESTAMP_PERM_ERROR;
}
}
if (fd >= 0)
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
debug_return_int(fd);
}
static ssize_t
ts_write(int fd, const char *fname, struct timestamp_entry *entry, off_t offset)
{
ssize_t nwritten;
off_t old_eof;
debug_decl(ts_write, SUDOERS_DEBUG_AUTH)
if (offset == -1) {
old_eof = lseek(fd, 0, SEEK_CUR);
nwritten = write(fd, entry, entry->size);
} else {
old_eof = offset;
#ifdef HAVE_PWRITE
nwritten = pwrite(fd, entry, entry->size, offset);
#else
if (lseek(fd, offset, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)offset);
nwritten = -1;
} else {
nwritten = write(fd, entry, entry->size);
}
#endif
}
if ((size_t)nwritten != entry->size) {
if (nwritten == -1) {
log_warning(SLOG_SEND_MAIL,
N_("unable to write to %s"), fname);
} else {
log_warningx(SLOG_SEND_MAIL,
N_("unable to write to %s"), fname);
}
if (nwritten > 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"short write, truncating partial time stamp record");
if (ftruncate(fd, old_eof) != 0) {
sudo_warn(U_("unable to truncate time stamp file to %lld bytes"),
(long long)old_eof);
}
}
debug_return_ssize_t(-1);
}
debug_return_ssize_t(nwritten);
}
static void
ts_init_key(struct timestamp_entry *entry, struct passwd *pw, int flags,
enum def_tuple ticket_type)
{
struct stat sb;
debug_decl(ts_init_key, SUDOERS_DEBUG_AUTH)
memset(entry, 0, sizeof(*entry));
entry->version = TS_VERSION;
entry->size = sizeof(*entry);
entry->flags = flags;
if (pw != NULL) {
entry->auth_uid = pw->pw_uid;
} else {
entry->flags |= TS_ANYUID;
}
entry->sid = user_sid;
switch (ticket_type) {
default:
sudo_warnx("unknown time stamp ticket type %d", ticket_type);
case tty:
if (user_ttypath != NULL && stat(user_ttypath, &sb) == 0) {
entry->type = TS_TTY;
entry->u.ttydev = sb.st_rdev;
if (entry->sid != -1)
get_starttime(entry->sid, &entry->start_time);
break;
}
case kernel:
case ppid:
entry->type = TS_PPID;
entry->u.ppid = getppid();
get_starttime(entry->u.ppid, &entry->start_time);
break;
case global:
entry->type = TS_GLOBAL;
break;
}
debug_return;
}
static void
ts_init_key_nonglobal(struct timestamp_entry *entry, struct passwd *pw, int flags)
{
ts_init_key(entry, pw, flags,
def_timestamp_type == ppid ? ppid : tty);
}
void *
timestamp_open(const char *user, pid_t sid)
{
struct ts_cookie *cookie;
char *fname = NULL;
int tries, fd = -1;
debug_decl(timestamp_open, SUDOERS_DEBUG_AUTH)
if (!sudo_timespecisset(&def_timestamp_timeout)) {
errno = ENOENT;
goto bad;
}
if (!ts_secure_dir(def_timestampdir, true, false))
goto bad;
if (asprintf(&fname, "%s/%s", def_timestampdir, user) == -1) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
for (tries = 1; ; tries++) {
struct stat sb;
fd = ts_open(fname, O_RDWR|O_CREAT);
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
log_warning(SLOG_SEND_MAIL, N_("unable to open %s"), fname);
goto bad;
case TIMESTAMP_PERM_ERROR:
goto bad;
}
if (tries == 1 && fstat(fd, &sb) == 0) {
struct timespec boottime, mtime, now;
if (sudo_gettime_real(&now) == 0 && get_boottime(&boottime)) {
if (sudo_timespeccmp(&now, &boottime, <)) {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"ignoring boot time that is in the future");
} else {
mtim_get(&sb, mtime);
if (sudo_timespeccmp(&mtime, &boottime, <)) {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
"removing time stamp file that predates boot time");
close(fd);
unlink(fname);
continue;
}
}
}
}
break;
}
cookie = malloc(sizeof(*cookie));
if (cookie == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
cookie->fd = fd;
cookie->fname = fname;
cookie->sid = sid;
cookie->pos = -1;
debug_return_ptr(cookie);
bad:
if (fd != -1)
close(fd);
free(fname);
debug_return_ptr(NULL);
}
static volatile sig_atomic_t got_signal;
static void
timestamp_handler(int s)
{
got_signal = s;
}
static bool
timestamp_lock_record(int fd, off_t pos, off_t len)
{
struct sigaction sa, saveint, savequit;
sigset_t mask, omask;
bool ret;
debug_decl(timestamp_lock_record, SUDOERS_DEBUG_AUTH)
if (pos >= 0 && lseek(fd, pos, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)pos);
debug_return_bool(false);
}
got_signal = 0;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = timestamp_handler;
(void) sigaction(SIGINT, &sa, &saveint);
(void) sigaction(SIGQUIT, &sa, &savequit);
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
(void) sigprocmask(SIG_UNBLOCK, &mask, &omask);
ret = sudo_lock_region(fd, SUDO_LOCK, len);
if (!ret) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"failed to lock fd %d [%lld, %lld]", fd,
(long long)pos, (long long)len);
}
(void) sigprocmask(SIG_SETMASK, &omask, NULL);
(void) sigaction(SIGINT, &saveint, NULL);
(void) sigaction(SIGQUIT, &savequit, NULL);
if (!ret && got_signal)
kill(getpid(), got_signal);
debug_return_bool(ret);
}
static bool
timestamp_unlock_record(int fd, off_t pos, off_t len)
{
debug_decl(timestamp_unlock_record, SUDOERS_DEBUG_AUTH)
if (pos >= 0 && lseek(fd, pos, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)pos);
debug_return_bool(false);
}
debug_return_bool(sudo_lock_region(fd, SUDO_UNLOCK, len));
}
static ssize_t
ts_read(struct ts_cookie *cookie, struct timestamp_entry *entry)
{
ssize_t nread = -1;
bool should_unlock = false;
debug_decl(ts_read, SUDOERS_DEBUG_AUTH)
if (!cookie->locked) {
if (!timestamp_lock_record(cookie->fd, cookie->pos, sizeof(*entry)))
goto done;
should_unlock = true;
}
#ifdef HAVE_PREAD
nread = pread(cookie->fd, entry, sizeof(*entry), cookie->pos);
#else
if (lseek(cookie->fd, cookie->pos, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)cookie->pos);
goto done;
}
nread = read(cookie->fd, entry, sizeof(*entry));
#endif
if (nread != sizeof(*entry)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"short read (%zd vs %zu), truncated time stamp file?",
nread, sizeof(*entry));
goto done;
}
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"read %zd byte record at %lld", nread, (long long)cookie->pos);
done:
if (should_unlock)
timestamp_unlock_record(cookie->fd, cookie->pos, sizeof(*entry));
debug_return_ssize_t(nread);
}
bool
timestamp_lock(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
struct timestamp_entry entry;
off_t lock_pos;
ssize_t nread;
debug_decl(timestamp_lock, SUDOERS_DEBUG_AUTH)
if (cookie == NULL) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"called with a NULL cookie!");
debug_return_bool(false);
}
if (!timestamp_lock_record(cookie->fd, 0, sizeof(struct timestamp_entry)))
debug_return_bool(false);
memset(&entry, 0, sizeof(entry));
nread = read(cookie->fd, &entry, sizeof(entry));
if (nread == 0) {
entry.version = TS_VERSION;
entry.size = sizeof(entry);
entry.type = TS_LOCKEXCL;
if (ts_write(cookie->fd, cookie->fname, &entry, -1) == -1)
debug_return_bool(false);
} else if (entry.type != TS_LOCKEXCL) {
entry.type = TS_LOCKEXCL;
memset((char *)&entry + offsetof(struct timestamp_entry, type), 0,
nread - offsetof(struct timestamp_entry, type));
if (ts_write(cookie->fd, cookie->fname, &entry, 0) == -1)
debug_return_bool(false);
}
if (entry.size != sizeof(entry)) {
if (lseek(cookie->fd, entry.size, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to seek to %lld", (long long)entry.size);
debug_return_bool(false);
}
}
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"searching for %s time stamp record",
def_timestamp_type == ppid ? "ppid" : "tty");
ts_init_key_nonglobal(&cookie->key, pw, TS_DISABLED);
if (ts_find_record(cookie->fd, &cookie->key, &entry)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"found existing %s time stamp record",
def_timestamp_type == ppid ? "ppid" : "tty");
lock_pos = lseek(cookie->fd, 0, SEEK_CUR) - (off_t)entry.size;
} else {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"appending new %s time stamp record",
def_timestamp_type == ppid ? "ppid" : "tty");
lock_pos = lseek(cookie->fd, 0, SEEK_CUR);
if (ts_write(cookie->fd, cookie->fname, &cookie->key, -1) == -1)
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"%s time stamp position is %lld",
def_timestamp_type == ppid ? "ppid" : "tty", (long long)lock_pos);
if (def_timestamp_type == global) {
cookie->locked = false;
cookie->key.type = TS_GLOBAL;
if (lseek(cookie->fd, 0, SEEK_SET) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
"unable to rewind fd");
debug_return_bool(false);
}
if (ts_find_record(cookie->fd, &cookie->key, &entry)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"found existing global record");
cookie->pos = lseek(cookie->fd, 0, SEEK_CUR) - (off_t)entry.size;
} else {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"appending new global record");
cookie->pos = lseek(cookie->fd, 0, SEEK_CUR);
if (ts_write(cookie->fd, cookie->fname, &cookie->key, -1) == -1)
debug_return_bool(false);
}
} else {
cookie->pos = lock_pos;
cookie->locked = true;
}
timestamp_unlock_record(cookie->fd, 0, sizeof(struct timestamp_entry));
if (!timestamp_lock_record(cookie->fd, lock_pos, sizeof(struct timestamp_entry)))
debug_return_bool(false);
debug_return_bool(true);
}
void
timestamp_close(void *vcookie)
{
struct ts_cookie *cookie = vcookie;
debug_decl(timestamp_close, SUDOERS_DEBUG_AUTH)
if (cookie != NULL) {
close(cookie->fd);
free(cookie->fname);
free(cookie);
}
debug_return;
}
int
timestamp_status(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
struct timestamp_entry entry;
struct timespec diff, now;
int status = TS_ERROR;
ssize_t nread;
debug_decl(timestamp_status, SUDOERS_DEBUG_AUTH)
if (!sudo_timespecisset(&def_timestamp_timeout)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"timestamps disabled");
status = TS_OLD;
goto done;
}
if (cookie == NULL || cookie->pos < 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"NULL cookie or invalid position");
status = TS_OLD;
goto done;
}
#ifdef TIOCCHKVERAUTH
if (def_timestamp_type == kernel) {
int fd = open(_PATH_TTY, O_RDWR);
if (fd != -1) {
if (ioctl(fd, TIOCCHKVERAUTH) == 0)
status = TS_CURRENT;
else
status = TS_OLD;
close(fd);
goto done;
}
}
#endif
if ((nread = ts_read(cookie, &entry)) != sizeof(entry))
goto done;
if (entry.version != TS_VERSION || entry.size != nread) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"invalid time stamp file @ %lld", (long long)cookie->pos);
status = TS_OLD;
goto done;
}
if (ISSET(entry.flags, TS_DISABLED)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record disabled");
status = TS_OLD;
goto done;
}
if (entry.type != TS_GLOBAL && entry.sid != cookie->sid) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record sid mismatch");
status = TS_OLD;
goto done;
}
sudo_timespecclear(&diff);
if (sudo_timespeccmp(&def_timestamp_timeout, &diff, <)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"time stamp record does not expire");
status = TS_CURRENT;
goto done;
}
if (sudo_gettime_mono(&now) == -1) {
log_warning(0, N_("unable to read the clock"));
status = TS_ERROR;
goto done;
}
sudo_timespecsub(&now, &entry.ts, &diff);
if (sudo_timespeccmp(&diff, &def_timestamp_timeout, <)) {
status = TS_CURRENT;
#if defined(CLOCK_MONOTONIC) || defined(__MACH__)
if (diff.tv_sec < 0) {
log_warningx(SLOG_SEND_MAIL,
N_("ignoring time stamp from the future"));
status = TS_OLD;
SET(entry.flags, TS_DISABLED);
(void)ts_write(cookie->fd, cookie->fname, &entry, cookie->pos);
}
#else
sudo_timespecsub(&entry.ts, &now, &diff);
diff.tv_nsec /= 2;
if (diff.tv_sec & 1)
diff.tv_nsec += 500000000;
diff.tv_sec /= 2;
while (diff.tv_nsec >= 1000000000) {
diff.tv_sec++;
diff.tv_nsec -= 1000000000;
}
if (sudo_timespeccmp(&diff, &def_timestamp_timeout, >)) {
time_t tv_sec = (time_t)entry.ts.tv_sec;
log_warningx(SLOG_SEND_MAIL,
N_("time stamp too far in the future: %20.20s"),
4 + ctime(&tv_sec));
status = TS_OLD;
SET(entry.flags, TS_DISABLED);
(void)ts_write(cookie->fd, cookie->fname, &entry, cookie->pos);
}
#endif
} else {
status = TS_OLD;
}
done:
debug_return_int(status);
}
bool
timestamp_update(void *vcookie, struct passwd *pw)
{
struct ts_cookie *cookie = vcookie;
int ret = false;
debug_decl(timestamp_update, SUDOERS_DEBUG_AUTH)
if (!sudo_timespecisset(&def_timestamp_timeout)) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"timestamps disabled");
goto done;
}
if (cookie == NULL || cookie->pos < 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"NULL cookie or invalid position");
goto done;
}
#ifdef TIOCSETVERAUTH
if (def_timestamp_type == kernel) {
int fd = open(_PATH_TTY, O_RDWR);
if (fd != -1) {
int secs = def_timestamp_timeout.tv_sec;
if (secs > 0) {
if (secs > 3600)
secs = 3600;
if (ioctl(fd, TIOCSETVERAUTH, &secs) != 0)
sudo_warn("TIOCSETVERAUTH");
}
close(fd);
goto done;
}
}
#endif
CLR(cookie->key.flags, TS_DISABLED);
if (sudo_gettime_mono(&cookie->key.ts) == -1) {
log_warning(0, N_("unable to read the clock"));
goto done;
}
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
"writing %zu byte record at %lld", sizeof(cookie->key),
(long long)cookie->pos);
if (ts_write(cookie->fd, cookie->fname, &cookie->key, cookie->pos) != -1)
ret = true;
done:
debug_return_int(ret);
}
int
timestamp_remove(bool unlink_it)
{
struct timestamp_entry key, entry;
int fd = -1, ret = true;
char *fname = NULL;
debug_decl(timestamp_remove, SUDOERS_DEBUG_AUTH)
#ifdef TIOCCLRVERAUTH
if (def_timestamp_type == kernel) {
fd = open(_PATH_TTY, O_RDWR);
if (fd != -1) {
ioctl(fd, TIOCCLRVERAUTH);
goto done;
}
}
#endif
if (asprintf(&fname, "%s/%s", def_timestampdir, user_name) == -1) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = -1;
goto done;
}
if (unlink_it) {
ret = unlink(fname) ? -1 : true;
goto done;
}
fd = ts_open(fname, O_RDWR);
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
if (errno != ENOENT)
ret = false;
goto done;
case TIMESTAMP_PERM_ERROR:
ret = -1;
goto done;
}
if (!timestamp_lock_record(fd, -1, sizeof(struct timestamp_entry))) {
sudo_warn(U_("unable to lock time stamp file %s"), fname);
ret = -1;
goto done;
}
ts_init_key(&key, NULL, 0, def_timestamp_type);
while (ts_find_record(fd, &key, &entry)) {
if (!ISSET(entry.flags, TS_DISABLED)) {
SET(entry.flags, TS_DISABLED);
if (lseek(fd, 0 - (off_t)sizeof(entry), SEEK_CUR) != -1) {
if (ts_write(fd, fname, &entry, -1) == -1)
ret = false;
}
}
}
done:
if (fd != -1)
close(fd);
free(fname);
debug_return_int(ret);
}
bool
already_lectured(int unused)
{
char status_file[PATH_MAX];
struct stat sb;
int len;
debug_decl(already_lectured, SUDOERS_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 && len < ssizeof(status_file)) {
debug_return_bool(stat(status_file, &sb) == 0);
}
log_warningx(SLOG_SEND_MAIL, N_("lecture status path too long: %s/%s"),
def_lecture_status_dir, user_name);
}
debug_return_bool(false);
}
int
set_lectured(void)
{
char lecture_status[PATH_MAX];
int len, fd, ret = false;
debug_decl(set_lectured, SUDOERS_DEBUG_AUTH)
len = snprintf(lecture_status, sizeof(lecture_status), "%s/%s",
def_lecture_status_dir, user_name);
if (len < 0 || len >= ssizeof(lecture_status)) {
log_warningx(SLOG_SEND_MAIL, N_("lecture status path too long: %s/%s"),
def_lecture_status_dir, user_name);
goto done;
}
if (!ts_secure_dir(def_lecture_status_dir, true, false))
goto done;
fd = ts_open(lecture_status, O_WRONLY|O_CREAT|O_EXCL);
switch (fd) {
case TIMESTAMP_OPEN_ERROR:
break;
case TIMESTAMP_PERM_ERROR:
ret = -1;
break;
default:
close(fd);
ret = true;
break;
}
done:
debug_return_int(ret);
}