xref: /openbsd-src/usr.sbin/rpki-client/rsync.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: rsync.c,v 1.24 2021/04/19 17:04:35 deraadt 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 <assert.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <poll.h>
26 #include <resolv.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <imsg.h>
33 
34 #include "extern.h"
35 
36 /*
37  * A running rsync process.
38  * We can have multiple of these simultaneously and need to keep track
39  * of which process maps to which request.
40  */
41 struct	rsyncproc {
42 	char	*uri; /* uri of this rsync proc */
43 	size_t	 id; /* identity of request */
44 	pid_t	 pid; /* pid of process or 0 if unassociated */
45 };
46 
47 /*
48  * Return the base of a rsync URI (rsync://hostname/module). The
49  * caRepository provided by the RIR CAs point deeper than they should
50  * which would result in many rsync calls for almost every subdirectory.
51  * This is inefficent so instead crop the URI to a common base.
52  * The returned string needs to be freed by the caller.
53  */
54 char *
55 rsync_base_uri(const char *uri)
56 {
57 	const char *host, *module, *rest;
58 	char *base_uri;
59 
60 	/* Case-insensitive rsync URI. */
61 	if (strncasecmp(uri, "rsync://", 8) != 0) {
62 		warnx("%s: not using rsync schema", uri);
63 		return NULL;
64 	}
65 
66 	/* Parse the non-zero-length hostname. */
67 	host = uri + 8;
68 
69 	if ((module = strchr(host, '/')) == NULL) {
70 		warnx("%s: missing rsync module", uri);
71 		return NULL;
72 	} else if (module == host) {
73 		warnx("%s: zero-length rsync host", uri);
74 		return NULL;
75 	}
76 
77 	/* The non-zero-length module follows the hostname. */
78 	module++;
79 	if (*module == '\0') {
80 		warnx("%s: zero-length rsync module", uri);
81 		return NULL;
82 	}
83 
84 	/* The path component is optional. */
85 	if ((rest = strchr(module, '/')) == NULL) {
86 		if ((base_uri = strdup(uri)) == NULL)
87 			err(1, NULL);
88 		return base_uri;
89 	} else if (rest == module) {
90 		warnx("%s: zero-length module", uri);
91 		return NULL;
92 	}
93 
94 	if ((base_uri = strndup(uri, rest - uri)) == NULL)
95 		err(1, NULL);
96 	return base_uri;
97 }
98 
99 static void
100 proc_child(int signal)
101 {
102 
103 	/* Nothing: just discard. */
104 }
105 
106 /*
107  * Process used for synchronising repositories.
108  * This simply waits to be told which repository to synchronise, then
109  * does so.
110  * It then responds with the identifier of the repo that it updated.
111  * It only exits cleanly when fd is closed.
112  * FIXME: limit the number of simultaneous process.
113  * Currently, an attacker can trivially specify thousands of different
114  * repositories and saturate our system.
115  */
116 void
117 proc_rsync(char *prog, char *bind_addr, int fd)
118 {
119 	size_t			 i, idsz = 0;
120 	int			 rc = 0;
121 	struct pollfd		 pfd;
122 	struct msgbuf		 msgq;
123 	sigset_t		 mask, oldmask;
124 	struct rsyncproc	*ids = NULL;
125 
126 	pfd.fd = fd;
127 
128 	msgbuf_init(&msgq);
129 	msgq.fd = fd;
130 
131 	/*
132 	 * Unveil the command we want to run.
133 	 * If this has a pathname component in it, interpret as a file
134 	 * and unveil the file directly.
135 	 * Otherwise, look up the command in our PATH.
136 	 */
137 
138 	if (strchr(prog, '/') == NULL) {
139 		const char *pp;
140 		char *save, *cmd, *path;
141 		struct stat stt;
142 
143 		if (getenv("PATH") == NULL)
144 			errx(1, "PATH is unset");
145 		if ((path = strdup(getenv("PATH"))) == NULL)
146 			err(1, NULL);
147 		save = path;
148 		while ((pp = strsep(&path, ":")) != NULL) {
149 			if (*pp == '\0')
150 				continue;
151 			if (asprintf(&cmd, "%s/%s", pp, prog) == -1)
152 				err(1, NULL);
153 			if (lstat(cmd, &stt) == -1) {
154 				free(cmd);
155 				continue;
156 			} else if (unveil(cmd, "x") == -1)
157 				err(1, "%s: unveil", cmd);
158 			free(cmd);
159 			break;
160 		}
161 		free(save);
162 	} else if (unveil(prog, "x") == -1)
163 		err(1, "%s: unveil", prog);
164 
165 	if (pledge("stdio proc exec", NULL) == -1)
166 		err(1, "pledge");
167 
168 	/* Initialise retriever for children exiting. */
169 
170 	if (sigemptyset(&mask) == -1)
171 		err(1, NULL);
172 	if (signal(SIGCHLD, proc_child) == SIG_ERR)
173 		err(1, NULL);
174 	if (sigaddset(&mask, SIGCHLD) == -1)
175 		err(1, NULL);
176 	if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1)
177 		err(1, NULL);
178 
179 	for (;;) {
180 		char *uri = NULL, *dst = NULL;
181 		ssize_t ssz;
182 		size_t id;
183 		pid_t pid;
184 		int st;
185 
186 		pfd.events = POLLIN;
187 		if (msgq.queued)
188 			pfd.events |= POLLOUT;
189 
190 		if (ppoll(&pfd, 1, NULL, &oldmask) == -1) {
191 			if (errno != EINTR)
192 				err(1, "ppoll");
193 
194 			/*
195 			 * If we've received an EINTR, it means that one
196 			 * of our children has exited and we can reap it
197 			 * and look up its identifier.
198 			 * Then we respond to the parent.
199 			 */
200 
201 			while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) {
202 				struct ibuf *b;
203 				int ok = 1;
204 
205 				for (i = 0; i < idsz; i++)
206 					if (ids[i].pid == pid)
207 						break;
208 				assert(i < idsz);
209 
210 				if (!WIFEXITED(st)) {
211 					warnx("rsync %s terminated abnormally",
212 					    ids[i].uri);
213 					rc = 1;
214 					ok = 0;
215 				} else if (WEXITSTATUS(st) != 0) {
216 					warnx("rsync %s failed", ids[i].uri);
217 					ok = 0;
218 				}
219 
220 				b = ibuf_open(sizeof(size_t) + sizeof(ok));
221 				if (b == NULL)
222 					err(1, NULL);
223 				io_simple_buffer(b, &ids[i].id, sizeof(size_t));
224 				io_simple_buffer(b, &ok, sizeof(ok));
225 				ibuf_close(&msgq, b);
226 
227 				free(ids[i].uri);
228 				ids[i].uri = NULL;
229 				ids[i].pid = 0;
230 				ids[i].id = 0;
231 			}
232 			if (pid == -1 && errno != ECHILD)
233 				err(1, "waitpid");
234 			continue;
235 		}
236 
237 		if (pfd.revents & POLLOUT) {
238 			switch (msgbuf_write(&msgq)) {
239 			case 0:
240 				errx(1, "write: connection closed");
241 			case -1:
242 				err(1, "write");
243 			}
244 		}
245 
246 		if (!(pfd.revents & POLLIN))
247 			continue;
248 
249 		/*
250 		 * Read til the parent exits.
251 		 * That will mean that we can safely exit.
252 		 */
253 
254 		if ((ssz = read(fd, &id, sizeof(size_t))) == -1)
255 			err(1, "read");
256 		if (ssz == 0)
257 			break;
258 
259 		/* Read host and module. */
260 
261 		io_str_read(fd, &dst);
262 		io_str_read(fd, &uri);
263 		assert(dst);
264 		assert(uri);
265 
266 		/* Run process itself, wait for exit, check error. */
267 
268 		if ((pid = fork()) == -1)
269 			err(1, "fork");
270 
271 		if (pid == 0) {
272 			char *args[32];
273 
274 			if (pledge("stdio exec", NULL) == -1)
275 				err(1, "pledge");
276 			i = 0;
277 			args[i++] = (char *)prog;
278 			args[i++] = "-rt";
279 			args[i++] = "--no-motd";
280 			args[i++] = "--timeout";
281 			args[i++] = "180";
282 			if (bind_addr != NULL) {
283 				args[i++] = "--address";
284 				args[i++] = (char *)bind_addr;
285 			}
286 			args[i++] = uri;
287 			args[i++] = dst;
288 			args[i] = NULL;
289 			/* XXX args overflow not prevented */
290 			execvp(args[0], args);
291 			err(1, "%s: execvp", prog);
292 		}
293 
294 		/* Augment the list of running processes. */
295 
296 		for (i = 0; i < idsz; i++)
297 			if (ids[i].pid == 0)
298 				break;
299 		if (i == idsz) {
300 			ids = reallocarray(ids, idsz + 1, sizeof(*ids));
301 			if (ids == NULL)
302 				err(1, NULL);
303 			idsz++;
304 		}
305 
306 		ids[i].id = id;
307 		ids[i].pid = pid;
308 		ids[i].uri = uri;
309 
310 		/* Clean up temporary values. */
311 
312 		free(dst);
313 	}
314 
315 	/* No need for these to be hanging around. */
316 	for (i = 0; i < idsz; i++)
317 		if (ids[i].pid > 0) {
318 			kill(ids[i].pid, SIGTERM);
319 			free(ids[i].uri);
320 		}
321 
322 	msgbuf_clear(&msgq);
323 	free(ids);
324 	exit(rc);
325 }
326