File: | src/usr.sbin/rpki-client/rsync.c |
Warning: | line 327, column 10 Use of memory after it is freed |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: rsync.c,v 1.48 2023/11/24 14:05:47 job Exp $ */ | ||||
2 | /* | ||||
3 | * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> | ||||
4 | * | ||||
5 | * Permission to use, copy, modify, and distribute this software for any | ||||
6 | * purpose with or without fee is hereby granted, provided that the above | ||||
7 | * copyright notice and this permission notice appear in all copies. | ||||
8 | * | ||||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
16 | */ | ||||
17 | |||||
18 | #include <sys/queue.h> | ||||
19 | #include <sys/stat.h> | ||||
20 | #include <sys/wait.h> | ||||
21 | #include <netinet/in.h> | ||||
22 | #include <err.h> | ||||
23 | #include <errno(*__errno()).h> | ||||
24 | #include <poll.h> | ||||
25 | #include <resolv.h> | ||||
26 | #include <signal.h> | ||||
27 | #include <stdio.h> | ||||
28 | #include <stdlib.h> | ||||
29 | #include <string.h> | ||||
30 | #include <unistd.h> | ||||
31 | #include <imsg.h> | ||||
32 | |||||
33 | #include "extern.h" | ||||
34 | |||||
35 | #define __STRINGIFY(x)"x" #x | ||||
36 | #define STRINGIFY(x)"x" __STRINGIFY(x)"x" | ||||
37 | |||||
38 | /* | ||||
39 | * A running rsync process. | ||||
40 | * We can have multiple of these simultaneously and need to keep track | ||||
41 | * of which process maps to which request. | ||||
42 | */ | ||||
43 | struct rsync { | ||||
44 | TAILQ_ENTRY(rsync)struct { struct rsync *tqe_next; struct rsync **tqe_prev; } entry; | ||||
45 | char *uri; /* uri of this rsync proc */ | ||||
46 | char *dst; /* destination directory */ | ||||
47 | char *compdst; /* compare against directory */ | ||||
48 | unsigned int id; /* identity of request */ | ||||
49 | pid_t pid; /* pid of process or 0 if unassociated */ | ||||
50 | }; | ||||
51 | |||||
52 | static TAILQ_HEAD(, rsync)struct { struct rsync *tqh_first; struct rsync **tqh_last; } states = TAILQ_HEAD_INITIALIZER(states){ ((void *)0), &(states).tqh_first }; | ||||
53 | |||||
54 | /* | ||||
55 | * Return the base of a rsync URI (rsync://hostname/module). The | ||||
56 | * caRepository provided by the RIR CAs point deeper than they should | ||||
57 | * which would result in many rsync calls for almost every subdirectory. | ||||
58 | * This is inefficient so instead crop the URI to a common base. | ||||
59 | * The returned string needs to be freed by the caller. | ||||
60 | */ | ||||
61 | char * | ||||
62 | rsync_base_uri(const char *uri) | ||||
63 | { | ||||
64 | const char *host, *module, *rest; | ||||
65 | char *base_uri; | ||||
66 | |||||
67 | /* Case-insensitive rsync URI. */ | ||||
68 | if (strncasecmp(uri, "rsync://", 8) != 0) { | ||||
69 | warnx("%s: not using rsync schema", uri); | ||||
70 | return NULL((void *)0); | ||||
71 | } | ||||
72 | |||||
73 | /* Parse the non-zero-length hostname. */ | ||||
74 | host = uri + 8; | ||||
75 | |||||
76 | if ((module = strchr(host, '/')) == NULL((void *)0)) { | ||||
77 | warnx("%s: missing rsync module", uri); | ||||
78 | return NULL((void *)0); | ||||
79 | } else if (module == host) { | ||||
80 | warnx("%s: zero-length rsync host", uri); | ||||
81 | return NULL((void *)0); | ||||
82 | } | ||||
83 | |||||
84 | /* The non-zero-length module follows the hostname. */ | ||||
85 | module++; | ||||
86 | if (*module == '\0') { | ||||
87 | warnx("%s: zero-length rsync module", uri); | ||||
88 | return NULL((void *)0); | ||||
89 | } | ||||
90 | |||||
91 | /* The path component is optional. */ | ||||
92 | if ((rest = strchr(module, '/')) == NULL((void *)0)) { | ||||
93 | if ((base_uri = strdup(uri)) == NULL((void *)0)) | ||||
94 | err(1, NULL((void *)0)); | ||||
95 | return base_uri; | ||||
96 | } else if (rest == module) { | ||||
97 | warnx("%s: zero-length module", uri); | ||||
98 | return NULL((void *)0); | ||||
99 | } | ||||
100 | |||||
101 | if ((base_uri = strndup(uri, rest - uri)) == NULL((void *)0)) | ||||
102 | err(1, NULL((void *)0)); | ||||
103 | return base_uri; | ||||
104 | } | ||||
105 | |||||
106 | /* | ||||
107 | * The directory passed as --compare-dest needs to be relative to | ||||
108 | * the destination directory. This function takes care of that. | ||||
109 | */ | ||||
110 | static char * | ||||
111 | rsync_fixup_dest(char *destdir, char *compdir) | ||||
112 | { | ||||
113 | const char *dotdot = "../../../../../../"; /* should be enough */ | ||||
114 | int dirs = 1; | ||||
115 | char *fn; | ||||
116 | char c; | ||||
117 | |||||
118 | while ((c = *destdir++) != '\0') | ||||
119 | if (c == '/') | ||||
120 | dirs++; | ||||
121 | |||||
122 | if (dirs > 6) | ||||
123 | /* too deep for us */ | ||||
124 | return NULL((void *)0); | ||||
125 | |||||
126 | if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1) | ||||
127 | err(1, NULL((void *)0)); | ||||
128 | return fn; | ||||
129 | } | ||||
130 | |||||
131 | static pid_t | ||||
132 | exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst, | ||||
133 | char *compdst) | ||||
134 | { | ||||
135 | pid_t pid; | ||||
136 | char *args[32]; | ||||
137 | char *reldst; | ||||
138 | int i; | ||||
139 | |||||
140 | if ((pid = fork()) == -1) | ||||
141 | err(1, "fork"); | ||||
142 | |||||
143 | if (pid == 0) { | ||||
144 | if (pledge("stdio exec", NULL((void *)0)) == -1) | ||||
145 | err(1, "pledge"); | ||||
146 | i = 0; | ||||
147 | args[i++] = (char *)prog; | ||||
148 | args[i++] = "-rtO"; | ||||
149 | args[i++] = "--no-motd"; | ||||
150 | args[i++] = "--min-size=" STRINGIFY(MIN_FILE_SIZE)"100"; | ||||
151 | args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE)"4000000"; | ||||
152 | args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT)"15"; | ||||
153 | args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT)"30"; | ||||
154 | args[i++] = "--include=*/"; | ||||
155 | args[i++] = "--include=*.cer"; | ||||
156 | args[i++] = "--include=*.crl"; | ||||
157 | args[i++] = "--include=*.gbr"; | ||||
158 | args[i++] = "--include=*.mft"; | ||||
159 | args[i++] = "--include=*.roa"; | ||||
160 | args[i++] = "--include=*.asa"; | ||||
161 | args[i++] = "--include=*.tak"; | ||||
162 | args[i++] = "--exclude=*"; | ||||
163 | if (bind_addr != NULL((void *)0)) { | ||||
164 | args[i++] = "--address"; | ||||
165 | args[i++] = (char *)bind_addr; | ||||
166 | } | ||||
167 | if (compdst != NULL((void *)0) && | ||||
168 | (reldst = rsync_fixup_dest(dst, compdst)) != NULL((void *)0)) { | ||||
169 | args[i++] = "--compare-dest"; | ||||
170 | args[i++] = reldst; | ||||
171 | } | ||||
172 | args[i++] = uri; | ||||
173 | args[i++] = dst; | ||||
174 | args[i] = NULL((void *)0); | ||||
175 | /* XXX args overflow not prevented */ | ||||
176 | execvp(args[0], args); | ||||
177 | err(1, "%s: execvp", prog); | ||||
178 | } | ||||
179 | |||||
180 | return pid; | ||||
181 | } | ||||
182 | |||||
183 | static void | ||||
184 | rsync_new(unsigned int id, char *uri, char *dst, char *compdst) | ||||
185 | { | ||||
186 | struct rsync *s; | ||||
187 | |||||
188 | if ((s = calloc(1, sizeof(*s))) == NULL((void *)0)) | ||||
189 | err(1, NULL((void *)0)); | ||||
190 | |||||
191 | s->id = id; | ||||
192 | s->uri = uri; | ||||
193 | s->dst = dst; | ||||
194 | s->compdst = compdst; | ||||
195 | |||||
196 | TAILQ_INSERT_TAIL(&states, s, entry)do { (s)->entry.tqe_next = ((void *)0); (s)->entry.tqe_prev = (&states)->tqh_last; *(&states)->tqh_last = ( s); (&states)->tqh_last = &(s)->entry.tqe_next; } while (0); | ||||
197 | } | ||||
198 | |||||
199 | static void | ||||
200 | rsync_free(struct rsync *s) | ||||
201 | { | ||||
202 | TAILQ_REMOVE(&states, s, entry)do { if (((s)->entry.tqe_next) != ((void *)0)) (s)->entry .tqe_next->entry.tqe_prev = (s)->entry.tqe_prev; else ( &states)->tqh_last = (s)->entry.tqe_prev; *(s)-> entry.tqe_prev = (s)->entry.tqe_next; ; ; } while (0); | ||||
203 | free(s->uri); | ||||
204 | free(s->dst); | ||||
205 | free(s->compdst); | ||||
206 | free(s); | ||||
207 | } | ||||
208 | |||||
209 | static void | ||||
210 | proc_child(int signal) | ||||
211 | { | ||||
212 | |||||
213 | /* Nothing: just discard. */ | ||||
214 | } | ||||
215 | |||||
216 | /* | ||||
217 | * Process used for synchronising repositories. | ||||
218 | * This simply waits to be told which repository to synchronise, then | ||||
219 | * does so. | ||||
220 | * It then responds with the identifier of the repo that it updated. | ||||
221 | * It only exits cleanly when fd is closed. | ||||
222 | */ | ||||
223 | void | ||||
224 | proc_rsync(char *prog, char *bind_addr, int fd) | ||||
225 | { | ||||
226 | int nprocs = 0, npending = 0, rc = 0; | ||||
227 | struct pollfd pfd; | ||||
228 | struct msgbuf msgq; | ||||
229 | struct ibuf *b, *inbuf = NULL((void *)0); | ||||
230 | sigset_t mask, oldmask; | ||||
231 | struct rsync *s, *ns; | ||||
232 | |||||
233 | if (pledge("stdio rpath proc exec unveil", NULL((void *)0)) == -1) | ||||
| |||||
234 | err(1, "pledge"); | ||||
235 | |||||
236 | pfd.fd = fd; | ||||
237 | msgbuf_init(&msgq); | ||||
238 | msgq.fd = fd; | ||||
239 | |||||
240 | /* | ||||
241 | * Unveil the command we want to run. | ||||
242 | * If this has a pathname component in it, interpret as a file | ||||
243 | * and unveil the file directly. | ||||
244 | * Otherwise, look up the command in our PATH. | ||||
245 | */ | ||||
246 | |||||
247 | if (strchr(prog, '/') == NULL((void *)0)) { | ||||
248 | const char *pp; | ||||
249 | char *save, *cmd, *path; | ||||
250 | struct stat stt; | ||||
251 | |||||
252 | if (getenv("PATH") == NULL((void *)0)) | ||||
253 | errx(1, "PATH is unset"); | ||||
254 | if ((path = strdup(getenv("PATH"))) == NULL((void *)0)) | ||||
255 | err(1, NULL((void *)0)); | ||||
256 | save = path; | ||||
257 | while ((pp = strsep(&path, ":")) != NULL((void *)0)) { | ||||
258 | if (*pp == '\0') | ||||
259 | continue; | ||||
260 | if (asprintf(&cmd, "%s/%s", pp, prog) == -1) | ||||
261 | err(1, NULL((void *)0)); | ||||
262 | if (lstat(cmd, &stt) == -1) { | ||||
263 | free(cmd); | ||||
264 | continue; | ||||
265 | } else if (unveil(cmd, "x") == -1) | ||||
266 | err(1, "%s: unveil", cmd); | ||||
267 | free(cmd); | ||||
268 | break; | ||||
269 | } | ||||
270 | free(save); | ||||
271 | } else if (unveil(prog, "x") == -1) | ||||
272 | err(1, "%s: unveil", prog); | ||||
273 | |||||
274 | if (pledge("stdio proc exec", NULL((void *)0)) == -1) | ||||
275 | err(1, "pledge"); | ||||
276 | |||||
277 | /* Initialise retriever for children exiting. */ | ||||
278 | |||||
279 | if (sigemptyset(&mask) == -1) | ||||
280 | err(1, NULL((void *)0)); | ||||
281 | if (signal(SIGCHLD20, proc_child) == SIG_ERR(void (*)(int))-1) | ||||
282 | err(1, NULL((void *)0)); | ||||
283 | if (sigaddset(&mask, SIGCHLD20) == -1) | ||||
284 | err(1, NULL((void *)0)); | ||||
285 | if (sigprocmask(SIG_BLOCK1, &mask, &oldmask) == -1) | ||||
286 | err(1, NULL((void *)0)); | ||||
287 | |||||
288 | for (;;) { | ||||
289 | char *uri, *dst, *compdst; | ||||
290 | unsigned int id; | ||||
291 | pid_t pid; | ||||
292 | int st; | ||||
293 | |||||
294 | pfd.events = 0; | ||||
295 | pfd.events |= POLLIN0x0001; | ||||
296 | if (msgq.queued
| ||||
297 | pfd.events |= POLLOUT0x0004; | ||||
298 | |||||
299 | if (npending
| ||||
300 | TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); ( s) = ((s)->entry.tqe_next)) { | ||||
301 | if (s->pid == 0) { | ||||
302 | s->pid = exec_rsync(prog, bind_addr, | ||||
303 | s->uri, s->dst, s->compdst); | ||||
304 | if (++nprocs >= MAX_RSYNC_REQUESTS16) | ||||
305 | break; | ||||
306 | if (--npending == 0) | ||||
307 | break; | ||||
308 | } | ||||
309 | } | ||||
310 | } | ||||
311 | |||||
312 | if (ppoll(&pfd, 1, NULL((void *)0), &oldmask) == -1) { | ||||
313 | if (errno(*__errno()) != EINTR4) | ||||
314 | err(1, "ppoll"); | ||||
315 | |||||
316 | /* | ||||
317 | * If we've received an EINTR, it means that one | ||||
318 | * of our children has exited and we can reap it | ||||
319 | * and look up its identifier. | ||||
320 | * Then we respond to the parent. | ||||
321 | */ | ||||
322 | |||||
323 | while ((pid = waitpid(WAIT_ANY(-1), &st, WNOHANG0x01)) > 0) { | ||||
324 | int ok = 1; | ||||
325 | |||||
326 | TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); ( s) = ((s)->entry.tqe_next)) | ||||
327 | if (s->pid == pid) | ||||
| |||||
328 | break; | ||||
329 | if (s == NULL((void *)0)) | ||||
330 | errx(1, "waitpid: %d unexpected", pid); | ||||
331 | |||||
332 | if (!WIFEXITED(st)(((st) & 0177) == 0)) { | ||||
333 | warnx("rsync %s terminated abnormally", | ||||
334 | s->uri); | ||||
335 | rc = 1; | ||||
336 | ok = 0; | ||||
337 | } else if (WEXITSTATUS(st)(int)(((unsigned)(st) >> 8) & 0xff) != 0) { | ||||
338 | warnx("rsync %s failed", s->uri); | ||||
339 | ok = 0; | ||||
340 | } | ||||
341 | |||||
342 | b = io_new_buffer(); | ||||
343 | io_simple_buffer(b, &s->id, sizeof(s->id)); | ||||
344 | io_simple_buffer(b, &ok, sizeof(ok)); | ||||
345 | io_close_buffer(&msgq, b); | ||||
346 | |||||
347 | rsync_free(s); | ||||
348 | nprocs--; | ||||
349 | } | ||||
350 | if (pid == -1 && errno(*__errno()) != ECHILD10) | ||||
351 | err(1, "waitpid"); | ||||
352 | |||||
353 | continue; | ||||
354 | } | ||||
355 | |||||
356 | if (pfd.revents & POLLOUT0x0004) { | ||||
357 | switch (msgbuf_write(&msgq)) { | ||||
358 | case 0: | ||||
359 | errx(1, "write: connection closed"); | ||||
360 | case -1: | ||||
361 | err(1, "write"); | ||||
362 | } | ||||
363 | } | ||||
364 | |||||
365 | /* connection closed */ | ||||
366 | if (pfd.revents & POLLHUP0x0010) | ||||
367 | break; | ||||
368 | |||||
369 | if (!(pfd.revents & POLLIN0x0001)) | ||||
370 | continue; | ||||
371 | |||||
372 | b = io_buf_read(fd, &inbuf); | ||||
373 | if (b == NULL((void *)0)) | ||||
374 | continue; | ||||
375 | |||||
376 | /* Read host and module. */ | ||||
377 | io_read_buf(b, &id, sizeof(id)); | ||||
378 | io_read_str(b, &dst); | ||||
379 | io_read_str(b, &compdst); | ||||
380 | io_read_str(b, &uri); | ||||
381 | |||||
382 | ibuf_free(b); | ||||
383 | |||||
384 | if (dst != NULL((void *)0)) { | ||||
385 | rsync_new(id, uri, dst, compdst); | ||||
386 | npending++; | ||||
387 | } else { | ||||
388 | TAILQ_FOREACH(s, &states, entry)for((s) = ((&states)->tqh_first); (s) != ((void *)0); ( s) = ((s)->entry.tqe_next)) | ||||
389 | if (s->id == id) | ||||
390 | break; | ||||
391 | if (s
| ||||
392 | if (s->pid != 0) | ||||
393 | kill(s->pid, SIGTERM15); | ||||
394 | else | ||||
395 | rsync_free(s); | ||||
396 | } | ||||
397 | } | ||||
398 | } | ||||
399 | |||||
400 | /* No need for these to be hanging around. */ | ||||
401 | TAILQ_FOREACH_SAFE(s, &states, entry, ns)for ((s) = ((&states)->tqh_first); (s) != ((void *)0) && ((ns) = ((s)->entry.tqe_next), 1); (s) = (ns)) { | ||||
402 | if (s->pid != 0) | ||||
403 | kill(s->pid, SIGTERM15); | ||||
404 | rsync_free(s); | ||||
405 | } | ||||
406 | |||||
407 | msgbuf_clear(&msgq); | ||||
408 | exit(rc); | ||||
409 | } |