]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
68c9e9f8507fdd9baed4e0ace82045846c25aa6f
[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
12 #include <sys/stat.h>
13 #include <sys/vfs.h>
14
15 #ifndef TIMESTAMP_DIR
16 # define TIMESTAMP_DIR "/tmp/doas"
17 #endif
18 #ifndef TMPFS_MAGIC
19 # define TMPFS_MAGIC 0x01021994
20 #endif
21
22 #define timespecisset(tsp)              ((tsp)->tv_sec || (tsp)->tv_nsec)
23 #define timespeccmp(tsp, usp, cmp)                                      \
24         (((tsp)->tv_sec == (usp)->tv_sec) ?                             \
25             ((tsp)->tv_nsec cmp (usp)->tv_nsec) :               \
26             ((tsp)->tv_sec cmp (usp)->tv_sec))
27 #define timespecadd(tsp, usp, vsp) do {                                         \
28                 (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \
29                 (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \
30                 if ((vsp)->tv_nsec >= 1000000000L) {                            \
31                         (vsp)->tv_sec++;                                                                \
32                         (vsp)->tv_nsec -= 1000000000L;                                  \
33                 }                                                                                                       \
34         } while (0)
35
36 static char *
37 tspath()
38 {
39         char *path, *tty, *ttynorm, *p;
40         if (!(tty = ttyname(0))
41             && !(tty = ttyname(1))
42                 && !(tty = ttyname(2)))
43                 err(1, "ttyname");
44         if (!(ttynorm = strdup(tty)))
45                 err(1, "strdup");
46         for (p = ttynorm; *p; p++)
47                 if (!isalnum(*p))
48                         *p = '_';
49         if (asprintf(&path, "%s/.%s_%d", TIMESTAMP_DIR, ttynorm, getppid()) == -1)
50                 errx(1, "asprintf");
51         free(ttynorm);
52         return path;
53 }
54
55 static int
56 checktsdir(const char *path)
57 {
58         char *dir, *buf;
59         struct stat st;
60         struct statfs sf;
61         gid_t gid;
62
63         if (!(buf = strdup(path)))
64                 err(1, "strdup");
65         dir = dirname(buf);
66
67 check:
68         if (lstat(dir, &st) == -1) {
69                 if (errno == ENOENT) {
70                         gid = getegid();
71                         if (setegid(0) != 0)
72                                 err(1, "setegid");
73                         if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
74                                 err(1, "mkdir");
75                         if (setegid(gid) != 0)
76                                 err(1, "setegid");
77                         goto check;
78                 } else {
79                         err(1, "stat");
80                 }
81         }
82
83         if ((st.st_mode & S_IFMT) != S_IFDIR)
84                 errx(1, "timestamp directory is not a directory");
85         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IWOTH|S_IROTH)) != 0)
86                 errx(1, "timestamp directory permissions wrong");
87         if (st.st_uid != 0 || st.st_gid != 0)
88                 errx(1, "timestamp directory is not owned by root");
89         if (statfs(dir, &sf) == -1)
90                 err(1, "statfs");
91         if (sf.f_type != TMPFS_MAGIC)
92                 errx(1, "timestamp directory not on tmpfs");
93
94         free(buf);
95         return 0;
96 }
97
98 static int
99 timestamp_read(int fd, struct timespec *mono, struct timespec *real)
100 {
101         if (read(fd, (void *)mono, sizeof *mono) != sizeof *mono ||
102             read(fd, (void *)real, sizeof *real) != sizeof *mono)
103                 err(1, "read");
104         if (!timespecisset(mono) || !timespecisset(real))
105                 errx(1, "timespecisset");
106         return 0;
107 }
108
109 int
110 persist_check(int fd, int secs)
111 {
112         struct timespec mono, real, ts_mono, ts_real, timeout;
113
114         if (timestamp_read(fd, &ts_mono, &ts_real) != 0)
115                 return 1;
116
117         if (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
118                 err(1, "clock_gettime(CLOCK_MONOTONIC, ?)");
119         if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
120                 err(1, "clock_gettime(CLOCK_REALTIME, ?)");
121
122         if (timespeccmp(&mono, &ts_mono, >) ||
123             timespeccmp(&real, &ts_real, >))
124                 return 1;
125
126         memset(&timeout, 0, sizeof timeout);
127         timeout.tv_sec = secs;
128         timespecadd(&timeout, &mono, &mono);
129         timespecadd(&timeout, &real, &real);
130
131         if (timespeccmp(&mono, &ts_mono, <) ||
132             timespeccmp(&real, &ts_real, <))
133                 errx(1, "timestamp is too far in the future");
134
135         return 0;
136 }
137
138 int
139 persist_set(int fd, int secs)
140 {
141         struct timespec mono, real, ts_mono, ts_real, timeout;
142
143         if (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
144                 err(1, "clock_gettime(XLOCK_MONOTONIC, ?)");
145         if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
146                 err(1, "clock_gettime(CLOCK_REALTIME, ?)");
147
148         memset(&timeout, 0, sizeof timeout);
149         timeout.tv_sec = secs;
150         timespecadd(&timeout, &mono, &ts_mono);
151         timespecadd(&timeout, &real, &ts_real);
152
153         if (lseek(fd, 0, SEEK_SET) == -1)
154                 err(1, "lseek");
155         if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
156             write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
157                 err(1, "write");
158
159         return 0;
160 }
161
162 int
163 persist_open(int *valid, int secs)
164 {
165         struct stat st;
166         int fd;
167         gid_t gid;
168         const char *path;
169
170         path = tspath();
171         if (checktsdir(path))
172                 errx(1, "checktsdir");
173
174         if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
175                 if (errno != ENOENT)
176                         err(1, "open: %s", path);
177
178         if (fd == -1) {
179                 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
180                         err(1, "open: %s", path);
181                 *valid = 0;
182                 return fd;
183         }
184
185         if (fstat(fd, &st) == -1)
186                 err(1, "stat");
187         if ((st.st_mode & S_IFMT) != S_IFREG)
188                 errx(1, "timestamp is not a file");
189         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
190                 errx(1, "timestamp permissions wrong");
191
192         gid = getegid();
193         if (st.st_uid != 0 || st.st_gid != gid)
194                 errx(1, "timestamp has wrong owner");
195
196         if (st.st_size == 0) {
197                 *valid = 0;
198                 return fd;
199         }
200
201         if (st.st_size != sizeof(struct timespec) * 2)
202                 errx(1, "corrupt timestamp file");
203
204         *valid = persist_check(fd, secs) == 0;
205
206         return fd;
207 }
208
209 int
210 persist_clear()
211 {
212         const char *path;
213         path = tspath();
214         if (unlink(path) == -1 && errno != ENOENT)
215                 return -1;
216         return 0;
217 }