]> git.armaanb.net Git - opendoas.git/blobdiff - persist_timestamp.c
persist_timestamp: move timespec macros to libopenbsd
[opendoas.git] / persist_timestamp.c
index 68c9e9f8507fdd9baed4e0ace82045846c25aa6f..bd32fe5dc0f982ad1ba710091de0b364853ab2f0 100644 (file)
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#if !defined(timespecisset) || \
+    !defined(timespeccmp) || \
+    !defined(timespecadd)
+#      include "sys-time.h"
+#endif
+
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <libgen.h>
+#include <limits.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
 
-#include <sys/stat.h>
-#include <sys/vfs.h>
+#include "includes.h"
 
 #ifndef TIMESTAMP_DIR
-# define TIMESTAMP_DIR "/tmp/doas"
+#      define TIMESTAMP_DIR "/tmp/doas"
+#endif
+
+#if defined(TIMESTAMP_TMPFS) && defined(__linux__)
+#      ifndef TMPFS_MAGIC
+#              define TMPFS_MAGIC 0x01021994
+#      endif
 #endif
-#ifndef TMPFS_MAGIC
-# define TMPFS_MAGIC 0x01021994
+
+#ifdef __linux__
+/* Use tty_nr from /proc/self/stat instead of using
+ * ttyname(3), stdin, stdout and stderr are user
+ * controllable and would allow to reuse timestamps
+ * from another writable terminal.
+ * See https://www.sudo.ws/alerts/tty_tickets.html
+ */
+static int
+ttynr()
+{
+       char buf[1024];
+       char *p, *saveptr;
+       const char *errstr;
+       int fd, n;
+
+       p = buf;
+
+       if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
+               return -1;
+
+       while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
+               if (n == -1) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       break;
+               }
+               p += n;
+               if (p >= buf + sizeof buf)
+                       break;
+       }
+       close(fd);
+
+       /* error if it contains NULL bytes */
+       if (n != 0 || memchr(buf, '\0', p - buf))
+               return -1;
+
+       /* Get the 7th field, 5 fields after the last ')',
+        * because the 5th field 'comm' can include spaces
+        * and closing paranthesis too.
+        * See https://www.sudo.ws/alerts/linux_tty.html
+        */
+       if ((p = strrchr(buf, ')')) == NULL)
+               return -1;
+       for ((p = strtok_r(p, " ", &saveptr)), n = 0; p && n < 5;
+           (p = strtok_r(NULL, " ", &saveptr)), n++)
+               ;
+       if (p == NULL || n != 5)
+               return -1;
+
+       n = strtonum(p, INT_MIN, INT_MAX, &errstr);
+       if (errstr)
+               return -1;
+
+       return n;
+}
+#else
+#error "ttynr not implemented"
 #endif
 
-#define        timespecisset(tsp)              ((tsp)->tv_sec || (tsp)->tv_nsec)
-#define        timespeccmp(tsp, usp, cmp)                                      \
-       (((tsp)->tv_sec == (usp)->tv_sec) ?                             \
-           ((tsp)->tv_nsec cmp (usp)->tv_nsec) :               \
-           ((tsp)->tv_sec cmp (usp)->tv_sec))
-#define        timespecadd(tsp, usp, vsp) do {                                         \
-               (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \
-               (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \
-               if ((vsp)->tv_nsec >= 1000000000L) {                            \
-                       (vsp)->tv_sec++;                                                                \
-                       (vsp)->tv_nsec -= 1000000000L;                                  \
-               }                                                                                                       \
-       } while (0)
-
-static char *
-tspath()
+static const char *
+tsname()
 {
-       char *path, *tty, *ttynorm, *p;
-       if (!(tty = ttyname(0))
-           && !(tty = ttyname(1))
-               && !(tty = ttyname(2)))
-               err(1, "ttyname");
-       if (!(ttynorm = strdup(tty)))
-               err(1, "strdup");
-       for (p = ttynorm; *p; p++)
-               if (!isalnum(*p))
-                       *p = '_';
-       if (asprintf(&path, "%s/.%s_%d", TIMESTAMP_DIR, ttynorm, getppid()) == -1)
-               errx(1, "asprintf");
-       free(ttynorm);
-       return path;
+       static char buf[128];
+       int tty;
+       pid_t ppid, sid;
+       if ((tty = ttynr()) == -1)
+               errx(1, "failed to get tty number");
+       ppid = getppid();
+       if ((sid = getsid(0)) == -1)
+               err(1, "getsid");
+       if (snprintf(buf, sizeof buf, ".%d_%d_%d", tty, ppid, sid) == -1)
+               return NULL;
+       return buf;
 }
 
 static int
-checktsdir(const char *path)
+checktsdir(int fd)
 {
-       char *dir, *buf;
        struct stat st;
+
+       if (fstat(fd, &st) == -1)
+               err(1, "fstatat");
+
+       if ((st.st_mode & S_IFMT) != S_IFDIR)
+               errx(0, "timestamp directory is not a directory");
+       if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
+               errx(1, "timestamp directory permissions wrong");
+       if (st.st_uid != 0 || st.st_gid != 0)
+               errx(1, "timestamp directory is not owned by root");
+
+#if defined(TIMESTAMP_TMPFS) && defined(__linux__)
        struct statfs sf;
-       gid_t gid;
+       if (fstatfs(fd, &sf) == -1)
+               err(1, "statfs");
+
+       if (sf.f_type != TMPFS_MAGIC)
+               errx(1, "timestamp directory not on tmpfs");
+#endif
 
-       if (!(buf = strdup(path)))
-               err(1, "strdup");
-       dir = dirname(buf);
+       return 0;
+}
 
-check:
-       if (lstat(dir, &st) == -1) {
+static int
+opentsdir()
+{
+       gid_t gid;
+       int fd;
+
+reopen:
+       if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
                if (errno == ENOENT) {
                        gid = getegid();
                        if (setegid(0) != 0)
                                err(1, "setegid");
-                       if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
+                       if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
                                err(1, "mkdir");
                        if (setegid(gid) != 0)
                                err(1, "setegid");
-                       goto check;
+                       goto reopen;
                } else {
-                       err(1, "stat");
+                       err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
                }
        }
 
-       if ((st.st_mode & S_IFMT) != S_IFDIR)
-               errx(1, "timestamp directory is not a directory");
-       if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IWOTH|S_IROTH)) != 0)
-               errx(1, "timestamp directory permissions wrong");
-       if (st.st_uid != 0 || st.st_gid != 0)
-               errx(1, "timestamp directory is not owned by root");
-       if (statfs(dir, &sf) == -1)
-               err(1, "statfs");
-       if (sf.f_type != TMPFS_MAGIC)
-               errx(1, "timestamp directory not on tmpfs");
+       if (checktsdir(fd) != 0)
+               return -1;
 
-       free(buf);
-       return 0;
+       return fd;
 }
 
 static int
-timestamp_read(int fd, struct timespec *mono, struct timespec *real)
+checktsfile(int fd, size_t *tssize)
 {
-       if (read(fd, (void *)mono, sizeof *mono) != sizeof *mono ||
-           read(fd, (void *)real, sizeof *real) != sizeof *mono)
-               err(1, "read");
-       if (!timespecisset(mono) || !timespecisset(real))
-               errx(1, "timespecisset");
+       struct stat st;
+       gid_t gid;
+
+       if (fstat(fd, &st) == -1)
+               err(1, "stat");
+       if ((st.st_mode & S_IFMT) != S_IFREG)
+               errx(1, "timestamp is not a regular file");
+       if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
+               errx(1, "timestamp has wrong permissions");
+
+       gid = getegid();
+       if (st.st_uid != 0 || st.st_gid != gid)
+               errx(1, "timestamp has wrong owner");
+
+       *tssize = st.st_size;
+
        return 0;
 }
 
-int
-persist_check(int fd, int secs)
+static int
+validts(int fd, int secs)
 {
        struct timespec mono, real, ts_mono, ts_real, timeout;
 
-       if (timestamp_read(fd, &ts_mono, &ts_real) != 0)
-               return 1;
+       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 (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
-               err(1, "clock_gettime(CLOCK_MONOTONIC, ?)");
-       if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
-               err(1, "clock_gettime(CLOCK_REALTIME, ?)");
+       if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
+           clock_gettime(CLOCK_REALTIME, &real) == -1)
+               err(1, "clock_gettime");
 
        if (timespeccmp(&mono, &ts_mono, >) ||
            timespeccmp(&real, &ts_real, >))
-               return 1;
+               return -1;
 
        memset(&timeout, 0, sizeof timeout);
        timeout.tv_sec = secs;
@@ -140,10 +217,9 @@ persist_set(int fd, int secs)
 {
        struct timespec mono, real, ts_mono, ts_real, timeout;
 
-       if (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
-               err(1, "clock_gettime(XLOCK_MONOTONIC, ?)");
-       if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
-               err(1, "clock_gettime(CLOCK_REALTIME, ?)");
+       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;
@@ -162,56 +238,56 @@ persist_set(int fd, int secs)
 int
 persist_open(int *valid, int secs)
 {
-       struct stat st;
-       int fd;
-       gid_t gid;
-       const char *path;
+       int dirfd, fd;
+       const char *name;
+
+       *valid = 0;
 
-       path = tspath();
-       if (checktsdir(path))
-               errx(1, "checktsdir");
+       if ((name = tsname()) == NULL)
+               errx(1, "failed to get timestamp name");
+       if ((dirfd = opentsdir()) == -1)
+               errx(1, "opentsdir");
 
-       if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
+       if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
                if (errno != ENOENT)
-                       err(1, "open: %s", path);
+                       err(1, "open timestamp file");
 
        if (fd == -1) {
-               if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
-                       err(1, "open: %s", path);
-               *valid = 0;
-               return fd;
+               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");
-       if ((st.st_mode & S_IFMT) != S_IFREG)
-               errx(1, "timestamp is not a file");
-       if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
-               errx(1, "timestamp permissions wrong");
-
-       gid = getegid();
-       if (st.st_uid != 0 || st.st_gid != gid)
-               errx(1, "timestamp has wrong owner");
-
-       if (st.st_size == 0) {
-               *valid = 0;
-               return fd;
-       }
-
-       if (st.st_size != sizeof(struct timespec) * 2)
+       size_t tssize;
+       if (checktsfile(fd, &tssize) == -1)
+               err(1, "checktsfile");
+
+       /* 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 (tssize == sizeof(struct timespec) * 2)
+               *valid = validts(fd, secs) == 0;
+       else if (tssize != 0)
                errx(1, "corrupt timestamp file");
 
-       *valid = persist_check(fd, secs) == 0;
-
+       close(dirfd);
        return fd;
 }
 
 int
 persist_clear()
 {
-       const char *path;
-       path = tspath();
-       if (unlink(path) == -1 && errno != ENOENT)
+       const char *name;
+       int dirfd;
+       if ((name = tsname()) == NULL)
+               errx(1, "failed to get timestamp name");
+       if ((dirfd = opentsdir()) == -1)
+               errx(1, "opentsdir");
+       if (unlinkat(dirfd, name, 0) == -1 && errno != ENOENT)
                return -1;
+       close(dirfd);
        return 0;
 }