+/*
+ * Copyright (c) 2020 Duncan Overbruck <mail@duncano.de>
+ *
+ * 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 "config.h"
+
/*
* 1) Timestamp files and directories
*
#include <time.h>
#include <unistd.h>
-#include "includes.h"
+#include "openbsd.h"
+#include "doas.h"
#ifndef TIMESTAMP_DIR
# define TIMESTAMP_DIR "/run/doas"
p = buf;
- if (snprintf(path, sizeof path, "/proc/%d/stat", pid) == -1)
+ n = snprintf(path, sizeof path, "/proc/%d/stat", pid);
+ if (n < 0 || n >= (int)sizeof path)
return -1;
- if ((fd = open(path, O_RDONLY)) == -1)
+ if ((fd = open(path, O_RDONLY|O_NOFOLLOW)) == -1) {
+ warn("failed to open: %s", path);
return -1;
+ }
- while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
+ while ((n = read(fd, p, buf + (sizeof buf - 1) - p)) != 0) {
if (n == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
+ warn("read: %s", path);
close(fd);
return -1;
}
p += n;
- if (p >= buf + sizeof buf)
+ if (p >= buf + (sizeof buf - 1))
break;
}
close(fd);
/* error if it contains NULL bytes */
- if (n != 0 || memchr(buf, '\0', p - buf))
+ if (n != 0 || memchr(buf, '\0', p - buf - 1) != NULL) {
+ warn("NUL in: %s", path);
return -1;
+ }
+
+ *p = '\0';
/* Get the 7th field, 5 fields after the last ')',
* (2th field) because the 5th field 'comm' can include
if (p == ep ||
(errno == ERANGE && *starttime == ULLONG_MAX))
return -1;
- break;
+ return 0;
}
- if (n == 23)
- break;
}
- return 0;
+ return -1;
}
#else
#error "proc_info not implemented"
#endif
-/* The session prefix is the tty number, the pid
- * of the session leader and the start time of the
- * session leader.
- */
-static const char *
-session_prefix()
-{
- static char prefix[128];
- int tty, fd;
- unsigned long long starttime;
- pid_t leader;
-
- if ((fd = open("/dev/tty", O_RDONLY)) == -1)
- err(1, "open: /dev/tty");
- if (ioctl(fd, TIOCGSID, &leader) == -1)
- err(1, "ioctl: failed to get session leader");
- close(fd);
-
- if (proc_info(leader, &tty, &starttime) == -1)
- errx(1, "failed to get tty number");
- if (snprintf(prefix, sizeof prefix, ".%d_%d_%llu_",
- tty, leader, starttime) == -1)
- return NULL;
- return prefix;
-}
-
-static const char *
-timestamp_name()
+static int
+timestamp_path(char *buf, size_t len)
{
- static char name[128];
pid_t ppid, sid;
- const char *prefix;
+ unsigned long long starttime;
+ int n, ttynr;
ppid = getppid();
if ((sid = getsid(0)) == -1)
- err(1, "getsid");
- if ((prefix = session_prefix()) == NULL)
- return NULL;
- if (snprintf(name, sizeof name, "%s_%d_%d", prefix, ppid, sid) == -1)
- return NULL;
- return name;
+ return -1;
+ if (proc_info(ppid, &ttynr, &starttime) == -1)
+ return -1;
+ n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d",
+ ppid, sid, ttynr, starttime, getuid());
+ if (n < 0 || n >= (int)len)
+ return -1;
+ return 0;
}
-static int
-opentsdir()
+int
+timestamp_set(int fd, int secs)
{
- struct stat st, stl;
- int fd;
- int isnew;
-
- isnew = 0;
-
-
-reopen:
- if (lstat(TIMESTAMP_DIR, &stl) == -1) {
- if (!(errno == ENOENT && isnew == 0))
- err(1, "lstat: %s", TIMESTAMP_DIR);
- } else if ((stl.st_mode & S_IFMT) != S_IFDIR) {
- errx(1, "%s: not a directory", TIMESTAMP_DIR);
- }
-
- if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
- if (errno == ENOENT && isnew == 0) {
- if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
- err(1, "mkdir");
- isnew = 1;
- goto reopen;
- } else {
- err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
- }
- }
-
-recheck:
- if (fstat(fd, &st) == -1)
- err(1, "fstatat");
-
- if (stl.st_ino != st.st_ino || stl.st_dev != st.st_dev)
- errx(1, "timestamp directory lstat and fstat are different files");
-
- if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
- errx(1, "timestamp directory has wrong permissions");
-
- if (isnew == 1) {
- if (fchown(fd, 0, 0) == -1)
- err(1, "fchown");
- isnew = 0;
- goto recheck;
- } else {
- if (st.st_uid != 0 || st.st_gid != 0)
- errx(1, "timestamp directory has wrong owner or group");
- }
+ struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
-#if defined(TIMESTAMP_TMPFS) && defined(__linux__)
- struct statfs sf;
- if (fstatfs(fd, &sf) == -1)
- err(1, "statfs");
-
- if (sf.f_type != TMPFS_MAGIC)
- errx(1, "timestamp directory not on tmpfs");
-#endif
+ if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
+ clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
+ return -1;
- return fd;
+ timespecadd(&ts[0], &timeout, &ts[0]);
+ timespecadd(&ts[1], &timeout, &ts[1]);
+ return futimens(fd, ts);
}
/*
* Returns 1 if the timestamp is valid, 0 if its invalid
*/
static int
-timestamp_valid(int fd, int secs)
+timestamp_check(int fd, int secs)
{
- struct timespec mono, real, ts_mono, ts_real, timeout;
+ struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
+ struct stat st;
- if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
- read(fd, &ts_real, sizeof ts_real) != sizeof ts_real)
- err(1, "read");
- if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
- errx(1, "timespecisset");
+ if (fstat(fd, &st) == -1)
+ err(1, "fstat");
+ if (st.st_uid != 0 || st.st_gid != getgid() || st.st_mode != (S_IFREG | 0000))
+ errx(1, "timestamp uid, gid or mode wrong");
- if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
- clock_gettime(CLOCK_REALTIME, &real) == -1)
- err(1, "clock_gettime");
+ /* this timestamp was created but never set, invalid but no error */
+ if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim))
+ return 0;
- if (timespeccmp(&mono, &ts_mono, >) ||
- timespeccmp(&real, &ts_real, >))
+ if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
+ clock_gettime(CLOCK_REALTIME, &ts[1]) == -1) {
+ warn("clock_gettime");
return 0;
+ }
- memset(&timeout, 0, sizeof timeout);
- timeout.tv_sec = secs;
- timespecadd(&timeout, &mono, &mono);
- timespecadd(&timeout, &real, &real);
+ /* check if timestamp is too old */
+ if (timespeccmp(&st.st_atim, &ts[0], <) ||
+ timespeccmp(&st.st_mtim, &ts[1], <))
+ return 0;
- if (timespeccmp(&mono, &ts_mono, <) ||
- timespeccmp(&real, &ts_real, <))
- errx(1, "timestamp is too far in the future");
+ /* check if timestamp is too far in the future */
+ timespecadd(&ts[0], &timeout, &ts[0]);
+ timespecadd(&ts[1], &timeout, &ts[1]);
+ if (timespeccmp(&st.st_atim, &ts[0], >) ||
+ timespeccmp(&st.st_mtim, &ts[1], >)) {
+ warnx("timestamp too far in the future");
+ return 0;
+ }
return 1;
}
-int
-timestamp_set(int fd, int secs)
-{
- struct timespec mono, real, ts_mono, ts_real, timeout;
-
- if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
- clock_gettime(CLOCK_REALTIME, &real) == -1)
- err(1, "clock_gettime");
-
- memset(&timeout, 0, sizeof timeout);
- timeout.tv_sec = secs;
- timespecadd(&timeout, &mono, &ts_mono);
- timespecadd(&timeout, &real, &ts_real);
-
- if (lseek(fd, 0, SEEK_SET) == -1)
- err(1, "lseek");
- if (ftruncate(fd, 0) == -1)
- err(1, "ftruncate");
- if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
- write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
- err(1, "write");
-
- return 0;
-}
-
int
timestamp_open(int *valid, int secs)
{
- struct stat st, stl;
- int dirfd, fd;
- gid_t gid;
- const char *name;
+ struct timespec ts[2] = {0};
+ struct stat st;
+ int fd;
+ char path[256];
+ int serrno = 0;
*valid = 0;
- if ((name = timestamp_name()) == NULL)
- errx(1, "failed to get timestamp name");
- if ((dirfd = opentsdir()) == -1)
- errx(1, "opentsdir");
-
- if (fstatat(dirfd, name, &stl, AT_SYMLINK_NOFOLLOW) == -1) {
+ if (stat(TIMESTAMP_DIR, &st) == -1) {
if (errno != ENOENT)
- err(1, "fstatat");
- } else if ((stl.st_mode & S_IFMT) != S_IFREG) {
- errx(1, "timestamp: not a regular file");
+ return -1;
+ if (mkdir(TIMESTAMP_DIR, 0700) == -1)
+ return -1;
+ } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
+ return -1;
}
- if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
- if (errno != ENOENT)
- err(1, "open timestamp file");
+ if (timestamp_path(path, sizeof path) == -1)
+ return -1;
- /*
- * If the file was opened, check if fstat and lstat results are
- * the same file.
- * If the file doesn't exist and we create it with O_CREAT|O_EXCL,
- * it is already known that the file is a regular file.
- */
- if (fd != -1) {
- if (fstat(fd, &st) == -1)
- err(1, "stat");
- if (stl.st_ino != st.st_ino || stl.st_dev != st.st_dev)
- errx(1, "timestamp file lstat and fstat are different files");
- } else {
- if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW),
- (S_IRUSR|S_IWUSR))) == -1)
- err(1, "open timestamp file");
- if (fstat(fd, &st) == -1)
- err(1, "stat");
- }
+ fd = open(path, O_RDONLY|O_NOFOLLOW);
+ if (fd == -1) {
+ char tmp[256];
+ int n;
- if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
- errx(1, "timestamp file has wrong permissions");
+ if (errno != ENOENT)
+ err(1, "open: %s", path);
- gid = getegid();
- if (st.st_uid != 0 || st.st_gid != gid)
- errx(1, "timestamp file has wrong owner or group");
+ n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid());
+ if (n < 0 || n >= (int)sizeof tmp)
+ return -1;
- /* The timestamp size is 0 if its a new file or a
- * timestamp that was never set, its not valid but
- * can be used to write the new timestamp.
- * If the size does not match the expected size it
- * is incomplete and should never be used
- */
- if (st.st_size == sizeof (struct timespec) * 2) {
- *valid = timestamp_valid(fd, secs);
- } else if (st.st_size == 0) {
- *valid = 0;
+ fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000);
+ if (fd == -1)
+ return -1;
+ if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) {
+ serrno = errno;
+ close(fd);
+ unlink(tmp);
+ errno = serrno;
+ return -1;
+ }
} else {
- errx(1, "corrupt timestamp file");
+ *valid = timestamp_check(fd, secs);
}
-
- close(dirfd);
return fd;
}
int
timestamp_clear()
{
- struct dirent *ent;
- DIR *dp;
- const char *prefix;
- int dirfd;
- int ret = 0;
- size_t plen;
-
- if ((prefix = session_prefix()) == NULL)
- errx(1, "failed to get timestamp session prefix");
- plen = strlen(prefix);
-
- if ((dirfd = opentsdir()) == -1)
- errx(1, "opentsdir");
- if ((dp = fdopendir(dirfd)) == NULL)
- err(1, "fopendir");
-
- /*
- * Delete all files in the timestamp directory
- * with the same session prefix.
- */
- while ((ent = readdir(dp)) != NULL) {
- if (ent->d_type != DT_REG)
- continue;
- if (strncmp(prefix, ent->d_name, plen) != 0)
- continue;
- if (unlinkat(dirfd, ent->d_name, 0) == -1)
- ret = -1;
- }
- closedir(dp);
- close(dirfd);
+ char path[256];
- return ret;
+ if (timestamp_path(path, sizeof path) == -1)
+ return -1;
+ if (unlink(path) == -1 && errno != ENOENT)
+ return -1;
+ return 0;
}