]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
persist_timestamp: make tmpfs requirement optional and only available on linux
[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, *p1, *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                         else
69                                 break;
70                 }
71                 p += n;
72                 if (p >= buf + sizeof buf)
73                         break;
74         }
75         close(fd);
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 ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
88             (p1 = strtok_r(NULL, " ", &saveptr)), n++)
89                 if (n == 5)
90                         break;
91         if (p1 == NULL || n != 5)
92                 return -1;
93
94         n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
95         if (errstr)
96                 return -1;
97
98         return n;
99 }
100 #else
101 #error "ttynr not implemented"
102 #endif
103
104 static char pathbuf[PATH_MAX];
105
106 static int
107 tspath(const char **path)
108 {
109         int tty;
110         pid_t ppid;
111         if (*pathbuf == '\0') {
112                 if ((tty = ttynr()) == -1)
113                         errx(1, "failed to get tty number");
114                 ppid = getppid();
115                 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
116                     TIMESTAMP_DIR, tty, ppid) == -1)
117                         return -1;
118         }
119         *path = pathbuf;
120         return 0;
121 }
122
123 static int
124 checktsdir(const char *path)
125 {
126         char *dir, *buf;
127         struct stat st;
128         struct statfs sf;
129         gid_t gid;
130
131         if (!(buf = strdup(path)))
132                 err(1, "strdup");
133         dir = dirname(buf);
134
135 check:
136         if (lstat(dir, &st) == -1) {
137                 if (errno == ENOENT) {
138                         gid = getegid();
139                         if (setegid(0) != 0)
140                                 err(1, "setegid");
141                         if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
142                                 err(1, "mkdir");
143                         if (setegid(gid) != 0)
144                                 err(1, "setegid");
145                         goto check;
146                 } else {
147                         err(1, "stat");
148                 }
149         }
150
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)
158                 err(1, "statfs");
159
160 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
161         if (sf.f_type != TMPFS_MAGIC)
162                 errx(1, "timestamp directory not on tmpfs");
163 #endif
164
165         free(buf);
166         return 0;
167 }
168
169 int
170 persist_check(int fd, int secs)
171 {
172         struct timespec mono, real, ts_mono, ts_real, timeout;
173
174         if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
175             read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
176                 err(1, "read");
177         if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
178                 errx(1, "timespecisset");
179
180         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
181             clock_gettime(CLOCK_REALTIME, &real) == -1)
182                 err(1, "clock_gettime");
183
184         if (timespeccmp(&mono, &ts_mono, >) ||
185             timespeccmp(&real, &ts_real, >))
186                 return -1;
187
188         memset(&timeout, 0, sizeof timeout);
189         timeout.tv_sec = secs;
190         timespecadd(&timeout, &mono, &mono);
191         timespecadd(&timeout, &real, &real);
192
193         if (timespeccmp(&mono, &ts_mono, <) ||
194             timespeccmp(&real, &ts_real, <))
195                 errx(1, "timestamp is too far in the future");
196
197         return 0;
198 }
199
200 int
201 persist_set(int fd, int secs)
202 {
203         struct timespec mono, real, ts_mono, ts_real, timeout;
204
205         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
206             clock_gettime(CLOCK_REALTIME, &real) == -1)
207                 err(1, "clock_gettime");
208
209         memset(&timeout, 0, sizeof timeout);
210         timeout.tv_sec = secs;
211         timespecadd(&timeout, &mono, &ts_mono);
212         timespecadd(&timeout, &real, &ts_real);
213
214         if (lseek(fd, 0, SEEK_SET) == -1)
215                 err(1, "lseek");
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)
218                 err(1, "write");
219
220         return 0;
221 }
222
223 int
224 persist_open(int *valid, int secs)
225 {
226         struct stat st;
227         int fd;
228         gid_t gid;
229         const char *path;
230
231         if (tspath(&path) == -1)
232                 errx(1, "failed to get timestamp path");
233         if (checktsdir(path))
234                 errx(1, "checktsdir");
235
236         if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
237                 if (errno != ENOENT)
238                         err(1, "open: %s", path);
239
240         if (fd == -1) {
241                 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
242                         err(1, "open: %s", path);
243                 *valid = 0;
244                 return fd;
245         }
246
247         if (fstat(fd, &st) == -1)
248                 err(1, "stat");
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");
253
254         gid = getegid();
255         if (st.st_uid != 0 || st.st_gid != gid)
256                 errx(1, "timestamp has wrong owner");
257
258         if (st.st_size == 0) {
259                 *valid = 0;
260                 return fd;
261         }
262
263         if (st.st_size != sizeof(struct timespec) * 2)
264                 errx(1, "corrupt timestamp file");
265
266         *valid = persist_check(fd, secs) == 0;
267
268         return fd;
269 }
270
271 int
272 persist_clear()
273 {
274         const char *path;
275         if (tspath(&path) == -1)
276                 errx(1, "failed to get timestamp path");
277         if (unlink(path) == -1 && errno != ENOENT)
278                 return -1;
279         return 0;
280 }