]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
ac16c8f727e916fdef2eb738b85748a8966ff143
[opendoas.git] / persist_timestamp.c
1 #include <ctype.h>
2 #include <err.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <libgen.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include <limits.h>
12 #include <string.h>
13
14 #include <sys/stat.h>
15 #include <sys/vfs.h>
16
17 #include "includes.h"
18
19 #ifndef TIMESTAMP_DIR
20 #       define TIMESTAMP_DIR "/tmp/doas"
21 #endif
22
23 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
24 #       ifndef TMPFS_MAGIC
25 #               define TMPFS_MAGIC 0x01021994
26 #       endif
27 #endif
28
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) {                            \
38                         (vsp)->tv_sec++;                                                                \
39                         (vsp)->tv_nsec -= 1000000000L;                                  \
40                 }                                                                                                       \
41         } while (0)
42
43
44 #ifdef __linux__
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
50  */
51 static int
52 ttynr()
53 {
54         char buf[1024];
55         char *p, *saveptr;
56         const char *errstr;
57         int fd, n;
58
59         p = buf;
60
61         if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
62                 return -1;
63
64         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
65                 if (n == -1) {
66                         if (errno == EAGAIN || errno == EINTR)
67                                 continue;
68                         break;
69                 }
70                 p += n;
71                 if (p >= buf + sizeof buf)
72                         break;
73         }
74         close(fd);
75
76         /* error if it contains NULL bytes */
77         if (n != 0 || memchr(buf, '\0', p - buf))
78                 return -1;
79
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
84          */
85         if ((p = strrchr(buf, ')')) == NULL)
86                 return -1;
87         for ((p = strtok_r(p, " ", &saveptr)), n = 0; p && n < 5;
88             (p = strtok_r(NULL, " ", &saveptr)), n++)
89                 ;
90         if (p == NULL || n != 5)
91                 return -1;
92
93         n = strtonum(p, INT_MIN, INT_MAX, &errstr);
94         if (errstr)
95                 return -1;
96
97         return n;
98 }
99 #else
100 #error "ttynr not implemented"
101 #endif
102
103 static const char *
104 tspath()
105 {
106         static char pathbuf[PATH_MAX];
107         int tty;
108         pid_t ppid, sid;
109         if ((tty = ttynr()) == -1)
110                 errx(1, "failed to get tty number");
111         ppid = getppid();
112         if ((sid = getsid(0)) == -1)
113                 err(1, "getsid");
114         if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d_%d",
115                 TIMESTAMP_DIR, tty, ppid, sid) == -1)
116                 return NULL;
117         return pathbuf;
118 }
119
120 static int
121 checktsdir(const char *path)
122 {
123         char *dir, *buf;
124         struct stat st;
125         struct statfs sf;
126         gid_t gid;
127
128         if (!(buf = strdup(path)))
129                 err(1, "strdup");
130         dir = dirname(buf);
131
132 check:
133         if (lstat(dir, &st) == -1) {
134                 if (errno == ENOENT) {
135                         gid = getegid();
136                         if (setegid(0) != 0)
137                                 err(1, "setegid");
138                         if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
139                                 err(1, "mkdir");
140                         if (setegid(gid) != 0)
141                                 err(1, "setegid");
142                         goto check;
143                 } else {
144                         err(1, "stat");
145                 }
146         }
147
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_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 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)
155                 err(1, "statfs");
156
157 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
158         if (sf.f_type != TMPFS_MAGIC)
159                 errx(1, "timestamp directory not on tmpfs");
160 #endif
161
162         free(buf);
163         return 0;
164 }
165
166 int
167 persist_check(int fd, int secs)
168 {
169         struct timespec mono, real, ts_mono, ts_real, timeout;
170
171         if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
172             read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
173                 err(1, "read");
174         if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
175                 errx(1, "timespecisset");
176
177         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
178             clock_gettime(CLOCK_REALTIME, &real) == -1)
179                 err(1, "clock_gettime");
180
181         if (timespeccmp(&mono, &ts_mono, >) ||
182             timespeccmp(&real, &ts_real, >))
183                 return -1;
184
185         memset(&timeout, 0, sizeof timeout);
186         timeout.tv_sec = secs;
187         timespecadd(&timeout, &mono, &mono);
188         timespecadd(&timeout, &real, &real);
189
190         if (timespeccmp(&mono, &ts_mono, <) ||
191             timespeccmp(&real, &ts_real, <))
192                 errx(1, "timestamp is too far in the future");
193
194         return 0;
195 }
196
197 int
198 persist_set(int fd, int secs)
199 {
200         struct timespec mono, real, ts_mono, ts_real, timeout;
201
202         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
203             clock_gettime(CLOCK_REALTIME, &real) == -1)
204                 err(1, "clock_gettime");
205
206         memset(&timeout, 0, sizeof timeout);
207         timeout.tv_sec = secs;
208         timespecadd(&timeout, &mono, &ts_mono);
209         timespecadd(&timeout, &real, &ts_real);
210
211         if (lseek(fd, 0, SEEK_SET) == -1)
212                 err(1, "lseek");
213         if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
214             write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
215                 err(1, "write");
216
217         return 0;
218 }
219
220 int
221 persist_open(int *valid, int secs)
222 {
223         struct stat st;
224         int fd;
225         gid_t gid;
226         const char *path;
227
228         if ((path = tspath()) == NULL)
229                 errx(1, "failed to get timestamp path");
230         if (checktsdir(path))
231                 errx(1, "checktsdir");
232
233         if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
234                 if (errno != ENOENT)
235                         err(1, "open: %s", path);
236
237         if (fd == -1) {
238                 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
239                         err(1, "open: %s", path);
240                 *valid = 0;
241                 return fd;
242         }
243
244         if (fstat(fd, &st) == -1)
245                 err(1, "stat");
246         if ((st.st_mode & S_IFMT) != S_IFREG)
247                 errx(1, "timestamp is not a file");
248         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
249                 errx(1, "timestamp permissions wrong");
250
251         gid = getegid();
252         if (st.st_uid != 0 || st.st_gid != gid)
253                 errx(1, "timestamp has wrong owner");
254
255         if (st.st_size == 0) {
256                 *valid = 0;
257                 return fd;
258         }
259
260         if (st.st_size != sizeof(struct timespec) * 2)
261                 errx(1, "corrupt timestamp file");
262
263         *valid = persist_check(fd, secs) == 0;
264
265         return fd;
266 }
267
268 int
269 persist_clear()
270 {
271         const char *path;
272         if ((path = tspath()) == NULL)
273                 errx(1, "failed to get timestamp path");
274         if (unlink(path) == -1 && errno != ENOENT)
275                 return -1;
276         return 0;
277 }