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