xref: /openbsd-src/usr.sbin/rpki-client/rsync.c (revision b5fa5d51bd4733ed62a748e18f6a838408441343)
1 /*	$OpenBSD: rsync.c,v 1.56 2024/11/21 13:32:27 claudio 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.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
36 #define	STRINGIFY(x)	__STRINGIFY(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)	 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)	states = TAILQ_HEAD_INITIALIZER(states);
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_PROTO, RSYNC_PROTO_LEN) != 0) {
69 		warnx("%s: not using rsync schema", uri);
70 		return NULL;
71 	}
72 
73 	/* Parse the non-zero-length hostname. */
74 	host = uri + RSYNC_PROTO_LEN;
75 
76 	if ((module = strchr(host, '/')) == NULL) {
77 		warnx("%s: missing rsync module", uri);
78 		return NULL;
79 	} else if (module == host) {
80 		warnx("%s: zero-length rsync host", uri);
81 		return NULL;
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;
89 	}
90 
91 	/* The path component is optional. */
92 	if ((rest = strchr(module, '/')) == NULL) {
93 		if ((base_uri = strdup(uri)) == NULL)
94 			err(1, NULL);
95 		return base_uri;
96 	} else if (rest == module) {
97 		warnx("%s: zero-length module", uri);
98 		return NULL;
99 	}
100 
101 	if ((base_uri = strndup(uri, rest - uri)) == NULL)
102 		err(1, NULL);
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;
125 
126 	if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1)
127 		err(1, NULL);
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) == -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);
151 		args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE);
152 		args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT);
153 		args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT);
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++] = "--include=*.spl";
163 		args[i++] = "--exclude=*";
164 		if (bind_addr != NULL) {
165 			args[i++] = "--address";
166 			args[i++] = (char *)bind_addr;
167 		}
168 		if (compdst != NULL &&
169 		    (reldst = rsync_fixup_dest(dst, compdst)) != NULL) {
170 			args[i++] = "--compare-dest";
171 			args[i++] = reldst;
172 		}
173 		args[i++] = uri;
174 		args[i++] = dst;
175 		args[i] = NULL;
176 		/* XXX args overflow not prevented */
177 		execvp(args[0], args);
178 		err(1, "%s: execvp", prog);
179 	}
180 
181 	return pid;
182 }
183 
184 static void
185 rsync_new(unsigned int id, char *uri, char *dst, char *compdst)
186 {
187 	struct rsync *s;
188 
189 	if ((s = calloc(1, sizeof(*s))) == NULL)
190 		err(1, NULL);
191 
192 	s->id = id;
193 	s->uri = uri;
194 	s->dst = dst;
195 	s->compdst = compdst;
196 
197 	TAILQ_INSERT_TAIL(&states, s, entry);
198 }
199 
200 static void
201 rsync_free(struct rsync *s)
202 {
203 	TAILQ_REMOVE(&states, s, entry);
204 	free(s->uri);
205 	free(s->dst);
206 	free(s->compdst);
207 	free(s);
208 }
209 
210 static void
211 proc_child(int signal)
212 {
213 
214 	/* Nothing: just discard. */
215 }
216 
217 /*
218  * Process used for synchronising repositories.
219  * This simply waits to be told which repository to synchronise, then
220  * does so.
221  * It then responds with the identifier of the repo that it updated.
222  * It only exits cleanly when fd is closed.
223  */
224 void
225 proc_rsync(char *prog, char *bind_addr, int fd)
226 {
227 	int			 nprocs = 0, npending = 0, rc = 0;
228 	struct pollfd		 pfd;
229 	struct msgbuf		*msgq;
230 	struct ibuf		*b;
231 	sigset_t		 mask, oldmask;
232 	struct rsync		*s, *ns;
233 
234 	if (pledge("stdio rpath proc exec unveil", NULL) == -1)
235 		err(1, "pledge");
236 
237 	if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) ==
238 	    NULL)
239 		err(1, NULL);
240 	pfd.fd = fd;
241 
242 	/*
243 	 * Unveil the command we want to run.
244 	 * If this has a pathname component in it, interpret as a file
245 	 * and unveil the file directly.
246 	 * Otherwise, look up the command in our PATH.
247 	 */
248 
249 	if (strchr(prog, '/') == NULL) {
250 		const char *pp;
251 		char *save, *cmd, *path;
252 		struct stat stt;
253 
254 		if (getenv("PATH") == NULL)
255 			errx(1, "PATH is unset");
256 		if ((path = strdup(getenv("PATH"))) == NULL)
257 			err(1, NULL);
258 		save = path;
259 		while ((pp = strsep(&path, ":")) != NULL) {
260 			if (*pp == '\0')
261 				continue;
262 			if (asprintf(&cmd, "%s/%s", pp, prog) == -1)
263 				err(1, NULL);
264 			if (lstat(cmd, &stt) == -1) {
265 				free(cmd);
266 				continue;
267 			} else if (unveil(cmd, "x") == -1)
268 				err(1, "%s: unveil", cmd);
269 			free(cmd);
270 			break;
271 		}
272 		free(save);
273 	} else if (unveil(prog, "x") == -1)
274 		err(1, "%s: unveil", prog);
275 
276 	if (pledge("stdio proc exec", NULL) == -1)
277 		err(1, "pledge");
278 
279 	/* Initialise retriever for children exiting. */
280 
281 	if (sigemptyset(&mask) == -1)
282 		err(1, NULL);
283 	if (signal(SIGCHLD, proc_child) == SIG_ERR)
284 		err(1, NULL);
285 	if (sigaddset(&mask, SIGCHLD) == -1)
286 		err(1, NULL);
287 	if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1)
288 		err(1, NULL);
289 
290 	for (;;) {
291 		char *uri, *dst, *compdst;
292 		unsigned int id;
293 		pid_t pid;
294 		int st;
295 
296 		pfd.events = 0;
297 		pfd.events |= POLLIN;
298 		if (msgbuf_queuelen(msgq) > 0)
299 			pfd.events |= POLLOUT;
300 
301 		if (npending > 0 && nprocs < MAX_RSYNC_REQUESTS) {
302 			TAILQ_FOREACH(s, &states, entry) {
303 				if (s->pid == 0) {
304 					s->pid = exec_rsync(prog, bind_addr,
305 					    s->uri, s->dst, s->compdst);
306 					if (++nprocs >= MAX_RSYNC_REQUESTS)
307 						break;
308 					if (--npending == 0)
309 						break;
310 				}
311 			}
312 		}
313 
314 		if (ppoll(&pfd, 1, NULL, &oldmask) == -1) {
315 			if (errno != EINTR)
316 				err(1, "ppoll");
317 
318 			/*
319 			 * If we've received an EINTR, it means that one
320 			 * of our children has exited and we can reap it
321 			 * and look up its identifier.
322 			 * Then we respond to the parent.
323 			 */
324 
325 			while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) {
326 				int ok = 1;
327 
328 				TAILQ_FOREACH(s, &states, entry)
329 					if (s->pid == pid)
330 						break;
331 				if (s == NULL)
332 					errx(1, "waitpid: %d unexpected", pid);
333 
334 				if (!WIFEXITED(st)) {
335 					warnx("rsync %s terminated abnormally",
336 					    s->uri);
337 					rc = 1;
338 					ok = 0;
339 				} else if (WEXITSTATUS(st) != 0) {
340 					warnx("rsync %s failed", s->uri);
341 					ok = 0;
342 				}
343 
344 				b = io_new_buffer();
345 				io_simple_buffer(b, &s->id, sizeof(s->id));
346 				io_simple_buffer(b, &ok, sizeof(ok));
347 				io_close_buffer(msgq, b);
348 
349 				rsync_free(s);
350 				nprocs--;
351 			}
352 			if (pid == -1 && errno != ECHILD)
353 				err(1, "waitpid");
354 
355 			continue;
356 		}
357 
358 		if (pfd.revents & POLLOUT) {
359 			if (msgbuf_write(fd, msgq) == -1) {
360 				if (errno == EPIPE)
361 					errx(1, "write: connection closed");
362 				else
363 					err(1, "write");
364 			}
365 		}
366 
367 		/* connection closed */
368 		if (pfd.revents & POLLHUP)
369 			break;
370 
371 		if (!(pfd.revents & POLLIN))
372 			continue;
373 
374 		switch (ibuf_read(fd, msgq)) {
375 		case -1:
376 			err(1, "ibuf_read");
377 		case 0:
378 			errx(1, "ibuf_read: connection closed");
379 		}
380 
381 		while ((b = io_buf_get(msgq)) != NULL) {
382 			/* Read host and module. */
383 			io_read_buf(b, &id, sizeof(id));
384 			io_read_str(b, &dst);
385 			io_read_str(b, &compdst);
386 			io_read_str(b, &uri);
387 
388 			ibuf_free(b);
389 
390 			if (dst != NULL) {
391 				rsync_new(id, uri, dst, compdst);
392 				npending++;
393 			} else {
394 				TAILQ_FOREACH(s, &states, entry)
395 					if (s->id == id)
396 						break;
397 				if (s != NULL) {
398 					if (s->pid != 0)
399 						kill(s->pid, SIGTERM);
400 					else
401 						rsync_free(s);
402 				}
403 			}
404 		}
405 	}
406 
407 	/* No need for these to be hanging around. */
408 	TAILQ_FOREACH_SAFE(s, &states, entry, ns) {
409 		if (s->pid != 0)
410 			kill(s->pid, SIGTERM);
411 		rsync_free(s);
412 	}
413 
414 	msgbuf_free(msgq);
415 	exit(rc);
416 }
417