20 # define TIMESTAMP_DIR "/tmp/doas"
23 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
25 # define TMPFS_MAGIC 0x01021994
29 #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec)
30 #define timespeccmp(tsp, usp, cmp) \
31 (((tsp)->tv_sec == (usp)->tv_sec) ? \
32 ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
33 ((tsp)->tv_sec cmp (usp)->tv_sec))
34 #define timespecadd(tsp, usp, vsp) do { \
35 (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
36 (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
37 if ((vsp)->tv_nsec >= 1000000000L) { \
39 (vsp)->tv_nsec -= 1000000000L; \
45 /* Use tty_nr from /proc/self/stat instead of using
46 * ttyname(3), stdin, stdout and stderr are user
47 * controllable and would allow to reuse timestamps
48 * from another writable terminal.
49 * See https://www.sudo.ws/alerts/tty_tickets.html
55 char *p, *p1, *saveptr;
61 if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
64 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
66 if (errno == EAGAIN || errno == EINTR)
72 if (p >= buf + sizeof buf)
76 /* error if it contains NULL bytes */
77 if (n != 0 || memchr(buf, '\0', p - buf))
80 /* Get the 7th field, 5 fields after the last ')',
81 * because the 5th field 'comm' can include spaces
82 * and closing paranthesis too.
83 * See https://www.sudo.ws/alerts/linux_tty.html
85 if ((p = strrchr(buf, ')')) == NULL)
87 for ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
88 (p1 = strtok_r(NULL, " ", &saveptr)), n++)
91 if (p1 == NULL || n != 5)
94 n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
101 #error "ttynr not implemented"
104 static char pathbuf[PATH_MAX];
107 tspath(const char **path)
111 if (*pathbuf == '\0') {
112 if ((tty = ttynr()) == -1)
113 errx(1, "failed to get tty number");
115 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
116 TIMESTAMP_DIR, tty, ppid) == -1)
124 checktsdir(const char *path)
131 if (!(buf = strdup(path)))
136 if (lstat(dir, &st) == -1) {
137 if (errno == ENOENT) {
141 if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
143 if (setegid(gid) != 0)
151 if ((st.st_mode & S_IFMT) != S_IFDIR)
152 errx(1, "timestamp directory is not a directory");
153 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
154 errx(1, "timestamp directory permissions wrong");
155 if (st.st_uid != 0 || st.st_gid != 0)
156 errx(1, "timestamp directory is not owned by root");
157 if (statfs(dir, &sf) == -1)
160 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
161 if (sf.f_type != TMPFS_MAGIC)
162 errx(1, "timestamp directory not on tmpfs");
170 persist_check(int fd, int secs)
172 struct timespec mono, real, ts_mono, ts_real, timeout;
174 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
175 read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
177 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
178 errx(1, "timespecisset");
180 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
181 clock_gettime(CLOCK_REALTIME, &real) == -1)
182 err(1, "clock_gettime");
184 if (timespeccmp(&mono, &ts_mono, >) ||
185 timespeccmp(&real, &ts_real, >))
188 memset(&timeout, 0, sizeof timeout);
189 timeout.tv_sec = secs;
190 timespecadd(&timeout, &mono, &mono);
191 timespecadd(&timeout, &real, &real);
193 if (timespeccmp(&mono, &ts_mono, <) ||
194 timespeccmp(&real, &ts_real, <))
195 errx(1, "timestamp is too far in the future");
201 persist_set(int fd, int secs)
203 struct timespec mono, real, ts_mono, ts_real, timeout;
205 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
206 clock_gettime(CLOCK_REALTIME, &real) == -1)
207 err(1, "clock_gettime");
209 memset(&timeout, 0, sizeof timeout);
210 timeout.tv_sec = secs;
211 timespecadd(&timeout, &mono, &ts_mono);
212 timespecadd(&timeout, &real, &ts_real);
214 if (lseek(fd, 0, SEEK_SET) == -1)
216 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
217 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
224 persist_open(int *valid, int secs)
231 if (tspath(&path) == -1)
232 errx(1, "failed to get timestamp path");
233 if (checktsdir(path))
234 errx(1, "checktsdir");
236 if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
238 err(1, "open: %s", path);
241 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
242 err(1, "open: %s", path);
247 if (fstat(fd, &st) == -1)
249 if ((st.st_mode & S_IFMT) != S_IFREG)
250 errx(1, "timestamp is not a file");
251 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
252 errx(1, "timestamp permissions wrong");
255 if (st.st_uid != 0 || st.st_gid != gid)
256 errx(1, "timestamp has wrong owner");
258 if (st.st_size == 0) {
263 if (st.st_size != sizeof(struct timespec) * 2)
264 errx(1, "corrupt timestamp file");
266 *valid = persist_check(fd, secs) == 0;
275 if (tspath(&path) == -1)
276 errx(1, "failed to get timestamp path");
277 if (unlink(path) == -1 && errno != ENOENT)