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
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)
71 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 ((p = strtok_r(p, " ", &saveptr)), n = 0; p && n < 5;
88 (p = strtok_r(NULL, " ", &saveptr)), n++)
90 if (p == NULL || n != 5)
93 n = strtonum(p, INT_MIN, INT_MAX, &errstr);
100 #error "ttynr not implemented"
106 static char buf[128];
109 if ((tty = ttynr()) == -1)
110 errx(1, "failed to get tty number");
112 if ((sid = getsid(0)) == -1)
114 if (snprintf(buf, sizeof buf, ".%d_%d_%d", tty, ppid, sid) == -1)
124 if (fstat(fd, &st) == -1)
127 if ((st.st_mode & S_IFMT) != S_IFDIR)
128 errx(0, "timestamp directory is not a directory");
129 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
130 errx(1, "timestamp directory permissions wrong");
131 if (st.st_uid != 0 || st.st_gid != 0)
132 errx(1, "timestamp directory is not owned by root");
134 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
136 if (fstatfs(fd, &sf) == -1)
139 if (sf.f_type != TMPFS_MAGIC)
140 errx(1, "timestamp directory not on tmpfs");
153 if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
154 if (errno == ENOENT) {
158 if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
160 if (setegid(gid) != 0)
164 err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
168 if (checktsdir(fd) != 0)
175 checktsfile(int fd, size_t *tssize)
180 if (fstat(fd, &st) == -1)
182 if ((st.st_mode & S_IFMT) != S_IFREG)
183 errx(1, "timestamp is not a file");
184 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
185 errx(1, "timestamp permissions wrong");
188 if (st.st_uid != 0 || st.st_gid != gid)
189 errx(1, "timestamp has wrong owner");
191 *tssize = st.st_size;
197 persist_check(int fd, int secs)
199 struct timespec mono, real, ts_mono, ts_real, timeout;
201 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
202 read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
204 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
205 errx(1, "timespecisset");
207 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
208 clock_gettime(CLOCK_REALTIME, &real) == -1)
209 err(1, "clock_gettime");
211 if (timespeccmp(&mono, &ts_mono, >) ||
212 timespeccmp(&real, &ts_real, >))
215 memset(&timeout, 0, sizeof timeout);
216 timeout.tv_sec = secs;
217 timespecadd(&timeout, &mono, &mono);
218 timespecadd(&timeout, &real, &real);
220 if (timespeccmp(&mono, &ts_mono, <) ||
221 timespeccmp(&real, &ts_real, <))
222 errx(1, "timestamp is too far in the future");
228 persist_set(int fd, int secs)
230 struct timespec mono, real, ts_mono, ts_real, timeout;
232 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
233 clock_gettime(CLOCK_REALTIME, &real) == -1)
234 err(1, "clock_gettime");
236 memset(&timeout, 0, sizeof timeout);
237 timeout.tv_sec = secs;
238 timespecadd(&timeout, &mono, &ts_mono);
239 timespecadd(&timeout, &real, &ts_real);
241 if (lseek(fd, 0, SEEK_SET) == -1)
243 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
244 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
251 persist_open(int *valid, int secs)
256 if ((name = tsname()) == NULL)
257 errx(1, "failed to get timestamp name");
258 if ((dirfd = opentsdir()) == -1)
259 errx(1, "opentsdir");
261 if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
263 err(1, "open: %s", name);
266 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
267 err(1, "open: %s", name);
273 if (checktsfile(fd, &tssize) == -1)
274 err(1, "checktsfile");
281 if (tssize != sizeof(struct timespec) * 2)
282 errx(1, "corrupt timestamp file");
284 *valid = persist_check(fd, secs) == 0;
295 if ((name = tsname()) == NULL)
296 errx(1, "failed to get timestamp name");
297 if ((dirfd = opentsdir()) == -1)
298 errx(1, "opentsdir");
299 if (unlinkat(dirfd, name, 0) == -1 && errno != ENOENT)