20 # define TIMESTAMP_DIR "/tmp/doas"
23 # define TMPFS_MAGIC 0x01021994
26 #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec)
27 #define timespeccmp(tsp, usp, cmp) \
28 (((tsp)->tv_sec == (usp)->tv_sec) ? \
29 ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
30 ((tsp)->tv_sec cmp (usp)->tv_sec))
31 #define timespecadd(tsp, usp, vsp) do { \
32 (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
33 (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
34 if ((vsp)->tv_nsec >= 1000000000L) { \
36 (vsp)->tv_nsec -= 1000000000L; \
42 /* Use tty_nr from /proc/self/stat instead of using
43 * ttyname(3), stdin, stdout and stderr are user
44 * controllable and would allow to reuse timestamps
45 * from another writable terminal.
46 * See https://www.sudo.ws/alerts/tty_tickets.html
52 char *p, *p1, *saveptr;
58 if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
61 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
63 if (errno == EAGAIN || errno == EINTR)
69 if (p >= buf + sizeof buf)
73 /* error if it contains NULL bytes */
74 if (n != 0 || memchr(buf, '\0', p - buf))
77 /* Get the 7th field, 5 fields after the last ')',
78 * because the 5th field 'comm' can include spaces
79 * and closing paranthesis too.
80 * See https://www.sudo.ws/alerts/linux_tty.html
82 if ((p = strrchr(buf, ')')) == NULL)
84 for ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
85 (p1 = strtok_r(NULL, " ", &saveptr)), n++)
88 if (p1 == NULL || n != 5)
91 n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
98 #error "ttynr not implemented"
101 static char pathbuf[PATH_MAX];
104 tspath(const char **path)
108 if (*pathbuf == '\0') {
109 if ((tty = ttynr()) == -1)
110 errx(1, "failed to get tty number");
112 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
113 TIMESTAMP_DIR, tty, ppid) == -1)
121 checktsdir(const char *path)
128 if (!(buf = strdup(path)))
133 if (lstat(dir, &st) == -1) {
134 if (errno == ENOENT) {
138 if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
140 if (setegid(gid) != 0)
148 if ((st.st_mode & S_IFMT) != S_IFDIR)
149 errx(1, "timestamp directory is not a directory");
150 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IWOTH|S_IROTH)) != 0)
151 errx(1, "timestamp directory permissions wrong");
152 if (st.st_uid != 0 || st.st_gid != 0)
153 errx(1, "timestamp directory is not owned by root");
154 if (statfs(dir, &sf) == -1)
156 if (sf.f_type != TMPFS_MAGIC)
157 errx(1, "timestamp directory not on tmpfs");
164 persist_check(int fd, int secs)
166 struct timespec mono, real, ts_mono, ts_real, timeout;
168 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
169 read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
171 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
172 errx(1, "timespecisset");
174 if (clock_gettime(CLOCK_MONOTONIC, &mono) == -1 ||
175 clock_gettime(CLOCK_REALTIME, &real) == -1)
176 err(1, "clock_gettime");
178 if (timespeccmp(&mono, &ts_mono, >) ||
179 timespeccmp(&real, &ts_real, >))
182 memset(&timeout, 0, sizeof timeout);
183 timeout.tv_sec = secs;
184 timespecadd(&timeout, &mono, &mono);
185 timespecadd(&timeout, &real, &real);
187 if (timespeccmp(&mono, &ts_mono, <) ||
188 timespeccmp(&real, &ts_real, <))
189 errx(1, "timestamp is too far in the future");
195 persist_set(int fd, int secs)
197 struct timespec mono, real, ts_mono, ts_real, timeout;
199 if (clock_gettime(CLOCK_MONOTONIC, &mono) == -1 ||
200 clock_gettime(CLOCK_REALTIME, &real) == -1)
201 err(1, "clock_gettime");
203 memset(&timeout, 0, sizeof timeout);
204 timeout.tv_sec = secs;
205 timespecadd(&timeout, &mono, &ts_mono);
206 timespecadd(&timeout, &real, &ts_real);
208 if (lseek(fd, 0, SEEK_SET) == -1)
210 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
211 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
218 persist_open(int *valid, int secs)
225 if (tspath(&path) == -1)
226 errx(1, "failed to get timestamp path");
227 if (checktsdir(path))
228 errx(1, "checktsdir");
230 if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
232 err(1, "open: %s", path);
235 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
236 err(1, "open: %s", path);
241 if (fstat(fd, &st) == -1)
243 if ((st.st_mode & S_IFMT) != S_IFREG)
244 errx(1, "timestamp is not a file");
245 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
246 errx(1, "timestamp permissions wrong");
249 if (st.st_uid != 0 || st.st_gid != gid)
250 errx(1, "timestamp has wrong owner");
252 if (st.st_size == 0) {
257 if (st.st_size != sizeof(struct timespec) * 2)
258 errx(1, "corrupt timestamp file");
260 *valid = persist_check(fd, secs) == 0;
269 if (tspath(&path) == -1)
270 errx(1, "failed to get timestamp path");
271 if (unlink(path) == -1 && errno != ENOENT)