]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
bd32fe5dc0f982ad1ba710091de0b364853ab2f0
[opendoas.git] / persist_timestamp.c
1 #include <sys/stat.h>
2 #include <sys/vfs.h>
3
4 #if !defined(timespecisset) || \
5     !defined(timespeccmp) || \
6     !defined(timespecadd)
7 #       include "sys-time.h"
8 #endif
9
10 #include <ctype.h>
11 #include <err.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <limits.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <time.h>
18 #include <unistd.h>
19
20 #include "includes.h"
21
22 #ifndef TIMESTAMP_DIR
23 #       define TIMESTAMP_DIR "/tmp/doas"
24 #endif
25
26 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
27 #       ifndef TMPFS_MAGIC
28 #               define TMPFS_MAGIC 0x01021994
29 #       endif
30 #endif
31
32 #ifdef __linux__
33 /* Use tty_nr from /proc/self/stat instead of using
34  * ttyname(3), stdin, stdout and stderr are user
35  * controllable and would allow to reuse timestamps
36  * from another writable terminal.
37  * See https://www.sudo.ws/alerts/tty_tickets.html
38  */
39 static int
40 ttynr()
41 {
42         char buf[1024];
43         char *p, *saveptr;
44         const char *errstr;
45         int fd, n;
46
47         p = buf;
48
49         if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
50                 return -1;
51
52         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
53                 if (n == -1) {
54                         if (errno == EAGAIN || errno == EINTR)
55                                 continue;
56                         break;
57                 }
58                 p += n;
59                 if (p >= buf + sizeof buf)
60                         break;
61         }
62         close(fd);
63
64         /* error if it contains NULL bytes */
65         if (n != 0 || memchr(buf, '\0', p - buf))
66                 return -1;
67
68         /* Get the 7th field, 5 fields after the last ')',
69          * because the 5th field 'comm' can include spaces
70          * and closing paranthesis too.
71          * See https://www.sudo.ws/alerts/linux_tty.html
72          */
73         if ((p = strrchr(buf, ')')) == NULL)
74                 return -1;
75         for ((p = strtok_r(p, " ", &saveptr)), n = 0; p && n < 5;
76             (p = strtok_r(NULL, " ", &saveptr)), n++)
77                 ;
78         if (p == NULL || n != 5)
79                 return -1;
80
81         n = strtonum(p, INT_MIN, INT_MAX, &errstr);
82         if (errstr)
83                 return -1;
84
85         return n;
86 }
87 #else
88 #error "ttynr not implemented"
89 #endif
90
91 static const char *
92 tsname()
93 {
94         static char buf[128];
95         int tty;
96         pid_t ppid, sid;
97         if ((tty = ttynr()) == -1)
98                 errx(1, "failed to get tty number");
99         ppid = getppid();
100         if ((sid = getsid(0)) == -1)
101                 err(1, "getsid");
102         if (snprintf(buf, sizeof buf, ".%d_%d_%d", tty, ppid, sid) == -1)
103                 return NULL;
104         return buf;
105 }
106
107 static int
108 checktsdir(int fd)
109 {
110         struct stat st;
111
112         if (fstat(fd, &st) == -1)
113                 err(1, "fstatat");
114
115         if ((st.st_mode & S_IFMT) != S_IFDIR)
116                 errx(0, "timestamp directory is not a directory");
117         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
118                 errx(1, "timestamp directory permissions wrong");
119         if (st.st_uid != 0 || st.st_gid != 0)
120                 errx(1, "timestamp directory is not owned by root");
121
122 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
123         struct statfs sf;
124         if (fstatfs(fd, &sf) == -1)
125                 err(1, "statfs");
126
127         if (sf.f_type != TMPFS_MAGIC)
128                 errx(1, "timestamp directory not on tmpfs");
129 #endif
130
131         return 0;
132 }
133
134 static int
135 opentsdir()
136 {
137         gid_t gid;
138         int fd;
139
140 reopen:
141         if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
142                 if (errno == ENOENT) {
143                         gid = getegid();
144                         if (setegid(0) != 0)
145                                 err(1, "setegid");
146                         if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
147                                 err(1, "mkdir");
148                         if (setegid(gid) != 0)
149                                 err(1, "setegid");
150                         goto reopen;
151                 } else {
152                         err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
153                 }
154         }
155
156         if (checktsdir(fd) != 0)
157                 return -1;
158
159         return fd;
160 }
161
162 static int
163 checktsfile(int fd, size_t *tssize)
164 {
165         struct stat st;
166         gid_t gid;
167
168         if (fstat(fd, &st) == -1)
169                 err(1, "stat");
170         if ((st.st_mode & S_IFMT) != S_IFREG)
171                 errx(1, "timestamp is not a regular file");
172         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
173                 errx(1, "timestamp has wrong permissions");
174
175         gid = getegid();
176         if (st.st_uid != 0 || st.st_gid != gid)
177                 errx(1, "timestamp has wrong owner");
178
179         *tssize = st.st_size;
180
181         return 0;
182 }
183
184 static int
185 validts(int fd, int secs)
186 {
187         struct timespec mono, real, ts_mono, ts_real, timeout;
188
189         if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
190             read(fd, &ts_real, sizeof ts_real) != sizeof ts_real)
191                 err(1, "read");
192         if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
193                 errx(1, "timespecisset");
194
195         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
196             clock_gettime(CLOCK_REALTIME, &real) == -1)
197                 err(1, "clock_gettime");
198
199         if (timespeccmp(&mono, &ts_mono, >) ||
200             timespeccmp(&real, &ts_real, >))
201                 return -1;
202
203         memset(&timeout, 0, sizeof timeout);
204         timeout.tv_sec = secs;
205         timespecadd(&timeout, &mono, &mono);
206         timespecadd(&timeout, &real, &real);
207
208         if (timespeccmp(&mono, &ts_mono, <) ||
209             timespeccmp(&real, &ts_real, <))
210                 errx(1, "timestamp is too far in the future");
211
212         return 0;
213 }
214
215 int
216 persist_set(int fd, int secs)
217 {
218         struct timespec mono, real, ts_mono, ts_real, timeout;
219
220         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
221             clock_gettime(CLOCK_REALTIME, &real) == -1)
222                 err(1, "clock_gettime");
223
224         memset(&timeout, 0, sizeof timeout);
225         timeout.tv_sec = secs;
226         timespecadd(&timeout, &mono, &ts_mono);
227         timespecadd(&timeout, &real, &ts_real);
228
229         if (lseek(fd, 0, SEEK_SET) == -1)
230                 err(1, "lseek");
231         if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
232             write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
233                 err(1, "write");
234
235         return 0;
236 }
237
238 int
239 persist_open(int *valid, int secs)
240 {
241         int dirfd, fd;
242         const char *name;
243
244         *valid = 0;
245
246         if ((name = tsname()) == NULL)
247                 errx(1, "failed to get timestamp name");
248         if ((dirfd = opentsdir()) == -1)
249                 errx(1, "opentsdir");
250
251         if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
252                 if (errno != ENOENT)
253                         err(1, "open timestamp file");
254
255         if (fd == -1) {
256                 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW),
257                     (S_IRUSR|S_IWUSR))) == -1)
258                         err(1, "open timestamp file");
259         }
260
261         size_t tssize;
262         if (checktsfile(fd, &tssize) == -1)
263                 err(1, "checktsfile");
264
265         /* The timestamp size is 0 if its a new file or a
266          * timestamp that was never set, its not valid but
267          * can be used to write the new timestamp.
268          * If the size does not match the expected size it
269          * is incomplete and should never be used
270          */
271         if (tssize == sizeof(struct timespec) * 2)
272                 *valid = validts(fd, secs) == 0;
273         else if (tssize != 0)
274                 errx(1, "corrupt timestamp file");
275
276         close(dirfd);
277         return fd;
278 }
279
280 int
281 persist_clear()
282 {
283         const char *name;
284         int dirfd;
285         if ((name = tsname()) == NULL)
286                 errx(1, "failed to get timestamp name");
287         if ((dirfd = opentsdir()) == -1)
288                 errx(1, "opentsdir");
289         if (unlinkat(dirfd, name, 0) == -1 && errno != ENOENT)
290                 return -1;
291         close(dirfd);
292         return 0;
293 }