]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
c608dca7f357f83452ed5485e9600cca43f02c3c
[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 #ifndef TMPFS_MAGIC
23 # define TMPFS_MAGIC 0x01021994
24 #endif
25
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) {                            \
35                         (vsp)->tv_sec++;                                                                \
36                         (vsp)->tv_nsec -= 1000000000L;                                  \
37                 }                                                                                                       \
38         } while (0)
39
40
41 #ifdef __linux__
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
47  */
48 static int
49 ttynr()
50 {
51         char buf[1024];
52         char *p, *p1, *saveptr;
53         const char *errstr;
54         int fd, n;
55
56         p = buf;
57
58         if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
59                 return -1;
60
61         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
62                 if (n == -1) {
63                         if (errno == EAGAIN || errno == EINTR)
64                                 continue;
65                         else
66                                 break;
67                 }
68                 p += n;
69                 if (p >= buf + sizeof buf)
70                         break;
71         }
72         close(fd);
73         /* error if it contains NULL bytes */
74         if (n != 0 || memchr(buf, '\0', p - buf))
75                 return -1;
76
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
81          */
82         if ((p = strrchr(buf, ')')) == NULL)
83                 return -1;
84         for ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
85             (p1 = strtok_r(NULL, " ", &saveptr)), n++)
86                 if (n == 5)
87                         break;
88         if (p1 == NULL || n != 5)
89                 return -1;
90
91         n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
92         if (errstr)
93                 return -1;
94
95         return n;
96 }
97 #else
98 #error "ttynr not implemented"
99 #endif
100
101 static char pathbuf[PATH_MAX];
102
103 static int
104 tspath(const char **path)
105 {
106         int tty;
107         pid_t ppid;
108         if (*pathbuf == '\0') {
109                 if ((tty = ttynr()) == -1)
110                         errx(1, "failed to get tty number");
111                 ppid = getppid();
112                 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
113                     TIMESTAMP_DIR, tty, ppid) == -1)
114                         return -1;
115         }
116         *path = pathbuf;
117         return 0;
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         if (sf.f_type != TMPFS_MAGIC)
157                 errx(1, "timestamp directory not on tmpfs");
158
159         free(buf);
160         return 0;
161 }
162
163 int
164 persist_check(int fd, int secs)
165 {
166         struct timespec mono, real, ts_mono, ts_real, timeout;
167
168         if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
169             read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
170                 err(1, "read");
171         if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
172                 errx(1, "timespecisset");
173
174         if (clock_gettime(CLOCK_MONOTONIC, &mono) == -1 ||
175             clock_gettime(CLOCK_REALTIME, &real) == -1)
176                 err(1, "clock_gettime");
177
178         if (timespeccmp(&mono, &ts_mono, >) ||
179             timespeccmp(&real, &ts_real, >))
180                 return -1;
181
182         memset(&timeout, 0, sizeof timeout);
183         timeout.tv_sec = secs;
184         timespecadd(&timeout, &mono, &mono);
185         timespecadd(&timeout, &real, &real);
186
187         if (timespeccmp(&mono, &ts_mono, <) ||
188             timespeccmp(&real, &ts_real, <))
189                 errx(1, "timestamp is too far in the future");
190
191         return 0;
192 }
193
194 int
195 persist_set(int fd, int secs)
196 {
197         struct timespec mono, real, ts_mono, ts_real, timeout;
198
199         if (clock_gettime(CLOCK_MONOTONIC, &mono) == -1 ||
200             clock_gettime(CLOCK_REALTIME, &real) == -1)
201                 err(1, "clock_gettime");
202
203         memset(&timeout, 0, sizeof timeout);
204         timeout.tv_sec = secs;
205         timespecadd(&timeout, &mono, &ts_mono);
206         timespecadd(&timeout, &real, &ts_real);
207
208         if (lseek(fd, 0, SEEK_SET) == -1)
209                 err(1, "lseek");
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)
212                 err(1, "write");
213
214         return 0;
215 }
216
217 int
218 persist_open(int *valid, int secs)
219 {
220         struct stat st;
221         int fd;
222         gid_t gid;
223         const char *path;
224
225         if (tspath(&path) == -1)
226                 errx(1, "failed to get timestamp path");
227         if (checktsdir(path))
228                 errx(1, "checktsdir");
229
230         if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
231                 if (errno != ENOENT)
232                         err(1, "open: %s", path);
233
234         if (fd == -1) {
235                 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
236                         err(1, "open: %s", path);
237                 *valid = 0;
238                 return fd;
239         }
240
241         if (fstat(fd, &st) == -1)
242                 err(1, "stat");
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");
247
248         gid = getegid();
249         if (st.st_uid != 0 || st.st_gid != gid)
250                 errx(1, "timestamp has wrong owner");
251
252         if (st.st_size == 0) {
253                 *valid = 0;
254                 return fd;
255         }
256
257         if (st.st_size != sizeof(struct timespec) * 2)
258                 errx(1, "corrupt timestamp file");
259
260         *valid = persist_check(fd, secs) == 0;
261
262         return fd;
263 }
264
265 int
266 persist_clear()
267 {
268         const char *path;
269         if (tspath(&path) == -1)
270                 errx(1, "failed to get timestamp path");
271         if (unlink(path) == -1 && errno != ENOENT)
272                 return -1;
273         return 0;
274 }