xref: /openbsd-src/usr.sbin/rpki-client/rsync.c (revision b5fa5d51bd4733ed62a748e18f6a838408441343)
1*b5fa5d51Sclaudio /*	$OpenBSD: rsync.c,v 1.56 2024/11/21 13:32:27 claudio Exp $ */
29a7e9e7fSjob /*
39a7e9e7fSjob  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
49a7e9e7fSjob  *
59a7e9e7fSjob  * Permission to use, copy, modify, and distribute this software for any
69a7e9e7fSjob  * purpose with or without fee is hereby granted, provided that the above
79a7e9e7fSjob  * copyright notice and this permission notice appear in all copies.
89a7e9e7fSjob  *
99a7e9e7fSjob  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
109a7e9e7fSjob  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
119a7e9e7fSjob  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
129a7e9e7fSjob  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
139a7e9e7fSjob  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
149a7e9e7fSjob  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
159a7e9e7fSjob  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
169a7e9e7fSjob  */
179a7e9e7fSjob 
1808db1177Sclaudio #include <sys/queue.h>
19cfc09c7bSclaudio #include <sys/stat.h>
20cfc09c7bSclaudio #include <sys/wait.h>
2128d71d25Sderaadt #include <netinet/in.h>
229a7e9e7fSjob #include <err.h>
23cfc09c7bSclaudio #include <errno.h>
24cfc09c7bSclaudio #include <poll.h>
259a7e9e7fSjob #include <resolv.h>
26cfc09c7bSclaudio #include <signal.h>
279a7e9e7fSjob #include <stdio.h>
289a7e9e7fSjob #include <stdlib.h>
299a7e9e7fSjob #include <string.h>
30cfc09c7bSclaudio #include <unistd.h>
3108db1177Sclaudio #include <imsg.h>
329a7e9e7fSjob 
339a7e9e7fSjob #include "extern.h"
349a7e9e7fSjob 
35542275b6Sjob #define	__STRINGIFY(x)	#x
36542275b6Sjob #define	STRINGIFY(x)	__STRINGIFY(x)
37542275b6Sjob 
389a7e9e7fSjob /*
39cfc09c7bSclaudio  * A running rsync process.
40cfc09c7bSclaudio  * We can have multiple of these simultaneously and need to keep track
41cfc09c7bSclaudio  * of which process maps to which request.
42cfc09c7bSclaudio  */
433e9f5857Sclaudio struct rsync {
443e9f5857Sclaudio 	TAILQ_ENTRY(rsync)	 entry;
45cfc09c7bSclaudio 	char			*uri; /* uri of this rsync proc */
463e9f5857Sclaudio 	char			*dst; /* destination directory */
473e9f5857Sclaudio 	char			*compdst; /* compare against directory */
48b6884e9fSclaudio 	unsigned int		 id; /* identity of request */
49cfc09c7bSclaudio 	pid_t			 pid; /* pid of process or 0 if unassociated */
50cfc09c7bSclaudio };
51cfc09c7bSclaudio 
523e9f5857Sclaudio static TAILQ_HEAD(, rsync)	states = TAILQ_HEAD_INITIALIZER(states);
533e9f5857Sclaudio 
54cfc09c7bSclaudio /*
55402543e6Sclaudio  * Return the base of a rsync URI (rsync://hostname/module). The
56402543e6Sclaudio  * caRepository provided by the RIR CAs point deeper than they should
57402543e6Sclaudio  * which would result in many rsync calls for almost every subdirectory.
583a50f0a9Sjmc  * This is inefficient so instead crop the URI to a common base.
59402543e6Sclaudio  * The returned string needs to be freed by the caller.
609a7e9e7fSjob  */
61402543e6Sclaudio char *
62402543e6Sclaudio rsync_base_uri(const char *uri)
639a7e9e7fSjob {
64402543e6Sclaudio 	const char *host, *module, *rest;
65ec185dadSclaudio 	char *base_uri;
669a7e9e7fSjob 
679a7e9e7fSjob 	/* Case-insensitive rsync URI. */
680610060dSjob 	if (strncasecmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) {
699a7e9e7fSjob 		warnx("%s: not using rsync schema", uri);
70402543e6Sclaudio 		return NULL;
719a7e9e7fSjob 	}
729a7e9e7fSjob 
739a7e9e7fSjob 	/* Parse the non-zero-length hostname. */
74b4964d69Stb 	host = uri + RSYNC_PROTO_LEN;
759a7e9e7fSjob 
769a7e9e7fSjob 	if ((module = strchr(host, '/')) == NULL) {
779a7e9e7fSjob 		warnx("%s: missing rsync module", uri);
78402543e6Sclaudio 		return NULL;
799a7e9e7fSjob 	} else if (module == host) {
809a7e9e7fSjob 		warnx("%s: zero-length rsync host", uri);
81402543e6Sclaudio 		return NULL;
829a7e9e7fSjob 	}
839a7e9e7fSjob 
849a7e9e7fSjob 	/* The non-zero-length module follows the hostname. */
859a7e9e7fSjob 	module++;
86402543e6Sclaudio 	if (*module == '\0') {
87402543e6Sclaudio 		warnx("%s: zero-length rsync module", uri);
88402543e6Sclaudio 		return NULL;
89402543e6Sclaudio 	}
909a7e9e7fSjob 
919a7e9e7fSjob 	/* The path component is optional. */
92402543e6Sclaudio 	if ((rest = strchr(module, '/')) == NULL) {
93ec185dadSclaudio 		if ((base_uri = strdup(uri)) == NULL)
94ec185dadSclaudio 			err(1, NULL);
95ec185dadSclaudio 		return base_uri;
96402543e6Sclaudio 	} else if (rest == module) {
979a7e9e7fSjob 		warnx("%s: zero-length module", uri);
98402543e6Sclaudio 		return NULL;
999a7e9e7fSjob 	}
1009a7e9e7fSjob 
101ec185dadSclaudio 	if ((base_uri = strndup(uri, rest - uri)) == NULL)
102ec185dadSclaudio 		err(1, NULL);
103ec185dadSclaudio 	return base_uri;
1049a7e9e7fSjob }
105cfc09c7bSclaudio 
106b435c97dSclaudio /*
107b435c97dSclaudio  * The directory passed as --compare-dest needs to be relative to
108b435c97dSclaudio  * the destination directory. This function takes care of that.
109b435c97dSclaudio  */
110b435c97dSclaudio static char *
111b435c97dSclaudio rsync_fixup_dest(char *destdir, char *compdir)
112b435c97dSclaudio {
113b435c97dSclaudio 	const char *dotdot = "../../../../../../";	/* should be enough */
114b435c97dSclaudio 	int dirs = 1;
115b435c97dSclaudio 	char *fn;
116b435c97dSclaudio 	char c;
117b435c97dSclaudio 
118b435c97dSclaudio 	while ((c = *destdir++) != '\0')
119b435c97dSclaudio 		if (c == '/')
120b435c97dSclaudio 			dirs++;
121b435c97dSclaudio 
122b435c97dSclaudio 	if (dirs > 6)
123b435c97dSclaudio 		/* too deep for us */
124b435c97dSclaudio 		return NULL;
125b435c97dSclaudio 
126b435c97dSclaudio 	if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1)
127b435c97dSclaudio 		err(1, NULL);
128b435c97dSclaudio 	return fn;
129b435c97dSclaudio }
130b435c97dSclaudio 
1313e9f5857Sclaudio static pid_t
1323e9f5857Sclaudio exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst,
1333e9f5857Sclaudio     char *compdst)
1343e9f5857Sclaudio {
1353e9f5857Sclaudio 	pid_t pid;
1363e9f5857Sclaudio 	char *args[32];
1373e9f5857Sclaudio 	char *reldst;
1383e9f5857Sclaudio 	int i;
1393e9f5857Sclaudio 
1403e9f5857Sclaudio 	if ((pid = fork()) == -1)
1413e9f5857Sclaudio 		err(1, "fork");
1423e9f5857Sclaudio 
1433e9f5857Sclaudio 	if (pid == 0) {
1443e9f5857Sclaudio 		if (pledge("stdio exec", NULL) == -1)
1453e9f5857Sclaudio 			err(1, "pledge");
1463e9f5857Sclaudio 		i = 0;
1473e9f5857Sclaudio 		args[i++] = (char *)prog;
148eb427634Sjob 		args[i++] = "-rtO";
1493e9f5857Sclaudio 		args[i++] = "--no-motd";
150aed5e91bSjob 		args[i++] = "--min-size=" STRINGIFY(MIN_FILE_SIZE);
1513e9f5857Sclaudio 		args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE);
1523e9f5857Sclaudio 		args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT);
1533e9f5857Sclaudio 		args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT);
1543e9f5857Sclaudio 		args[i++] = "--include=*/";
1553e9f5857Sclaudio 		args[i++] = "--include=*.cer";
1563e9f5857Sclaudio 		args[i++] = "--include=*.crl";
1573e9f5857Sclaudio 		args[i++] = "--include=*.gbr";
1583e9f5857Sclaudio 		args[i++] = "--include=*.mft";
1593e9f5857Sclaudio 		args[i++] = "--include=*.roa";
1603e9f5857Sclaudio 		args[i++] = "--include=*.asa";
161ee2a33daSjob 		args[i++] = "--include=*.tak";
16253ff252dSjob 		args[i++] = "--include=*.spl";
1633e9f5857Sclaudio 		args[i++] = "--exclude=*";
1643e9f5857Sclaudio 		if (bind_addr != NULL) {
1653e9f5857Sclaudio 			args[i++] = "--address";
1663e9f5857Sclaudio 			args[i++] = (char *)bind_addr;
1673e9f5857Sclaudio 		}
1683e9f5857Sclaudio 		if (compdst != NULL &&
1693e9f5857Sclaudio 		    (reldst = rsync_fixup_dest(dst, compdst)) != NULL) {
1703e9f5857Sclaudio 			args[i++] = "--compare-dest";
1713e9f5857Sclaudio 			args[i++] = reldst;
1723e9f5857Sclaudio 		}
1733e9f5857Sclaudio 		args[i++] = uri;
1743e9f5857Sclaudio 		args[i++] = dst;
1753e9f5857Sclaudio 		args[i] = NULL;
1763e9f5857Sclaudio 		/* XXX args overflow not prevented */
1773e9f5857Sclaudio 		execvp(args[0], args);
1783e9f5857Sclaudio 		err(1, "%s: execvp", prog);
1793e9f5857Sclaudio 	}
1803e9f5857Sclaudio 
1813e9f5857Sclaudio 	return pid;
1823e9f5857Sclaudio }
1833e9f5857Sclaudio 
1843e9f5857Sclaudio static void
1853e9f5857Sclaudio rsync_new(unsigned int id, char *uri, char *dst, char *compdst)
1863e9f5857Sclaudio {
1873e9f5857Sclaudio 	struct rsync *s;
1883e9f5857Sclaudio 
1893e9f5857Sclaudio 	if ((s = calloc(1, sizeof(*s))) == NULL)
1903e9f5857Sclaudio 		err(1, NULL);
1913e9f5857Sclaudio 
1923e9f5857Sclaudio 	s->id = id;
1933e9f5857Sclaudio 	s->uri = uri;
1943e9f5857Sclaudio 	s->dst = dst;
1953e9f5857Sclaudio 	s->compdst = compdst;
1963e9f5857Sclaudio 
1973e9f5857Sclaudio 	TAILQ_INSERT_TAIL(&states, s, entry);
1983e9f5857Sclaudio }
1993e9f5857Sclaudio 
2003e9f5857Sclaudio static void
2013e9f5857Sclaudio rsync_free(struct rsync *s)
2023e9f5857Sclaudio {
2033e9f5857Sclaudio 	TAILQ_REMOVE(&states, s, entry);
2043e9f5857Sclaudio 	free(s->uri);
2053e9f5857Sclaudio 	free(s->dst);
2063e9f5857Sclaudio 	free(s->compdst);
2073e9f5857Sclaudio 	free(s);
2083e9f5857Sclaudio }
2093e9f5857Sclaudio 
210cfc09c7bSclaudio static void
211cfc09c7bSclaudio proc_child(int signal)
212cfc09c7bSclaudio {
213cfc09c7bSclaudio 
214cfc09c7bSclaudio 	/* Nothing: just discard. */
215cfc09c7bSclaudio }
216cfc09c7bSclaudio 
217cfc09c7bSclaudio /*
218cfc09c7bSclaudio  * Process used for synchronising repositories.
219cfc09c7bSclaudio  * This simply waits to be told which repository to synchronise, then
220cfc09c7bSclaudio  * does so.
221cfc09c7bSclaudio  * It then responds with the identifier of the repo that it updated.
222cfc09c7bSclaudio  * It only exits cleanly when fd is closed.
223cfc09c7bSclaudio  */
224cfc09c7bSclaudio void
225cfc09c7bSclaudio proc_rsync(char *prog, char *bind_addr, int fd)
226cfc09c7bSclaudio {
2273e9f5857Sclaudio 	int			 nprocs = 0, npending = 0, rc = 0;
228cfc09c7bSclaudio 	struct pollfd		 pfd;
22925d36c5cSclaudio 	struct msgbuf		*msgq;
230*b5fa5d51Sclaudio 	struct ibuf		*b;
231cfc09c7bSclaudio 	sigset_t		 mask, oldmask;
2323e9f5857Sclaudio 	struct rsync		*s, *ns;
233cfc09c7bSclaudio 
2341db5fd2bSclaudio 	if (pledge("stdio rpath proc exec unveil", NULL) == -1)
2351db5fd2bSclaudio 		err(1, "pledge");
23608db1177Sclaudio 
237*b5fa5d51Sclaudio 	if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) ==
238*b5fa5d51Sclaudio 	    NULL)
23925d36c5cSclaudio 		err(1, NULL);
240a6a6bc2cSclaudio 	pfd.fd = fd;
241cfc09c7bSclaudio 
242cfc09c7bSclaudio 	/*
243cfc09c7bSclaudio 	 * Unveil the command we want to run.
244cfc09c7bSclaudio 	 * If this has a pathname component in it, interpret as a file
245cfc09c7bSclaudio 	 * and unveil the file directly.
246cfc09c7bSclaudio 	 * Otherwise, look up the command in our PATH.
247cfc09c7bSclaudio 	 */
248cfc09c7bSclaudio 
249cfc09c7bSclaudio 	if (strchr(prog, '/') == NULL) {
25095b65f7cSderaadt 		const char *pp;
25195b65f7cSderaadt 		char *save, *cmd, *path;
25295b65f7cSderaadt 		struct stat stt;
25395b65f7cSderaadt 
254cfc09c7bSclaudio 		if (getenv("PATH") == NULL)
255cfc09c7bSclaudio 			errx(1, "PATH is unset");
256cfc09c7bSclaudio 		if ((path = strdup(getenv("PATH"))) == NULL)
2572c3e7bceSclaudio 			err(1, NULL);
258cfc09c7bSclaudio 		save = path;
259cfc09c7bSclaudio 		while ((pp = strsep(&path, ":")) != NULL) {
260cfc09c7bSclaudio 			if (*pp == '\0')
261cfc09c7bSclaudio 				continue;
262cfc09c7bSclaudio 			if (asprintf(&cmd, "%s/%s", pp, prog) == -1)
2632c3e7bceSclaudio 				err(1, NULL);
264cfc09c7bSclaudio 			if (lstat(cmd, &stt) == -1) {
265cfc09c7bSclaudio 				free(cmd);
266cfc09c7bSclaudio 				continue;
267cfc09c7bSclaudio 			} else if (unveil(cmd, "x") == -1)
268cfc09c7bSclaudio 				err(1, "%s: unveil", cmd);
269cfc09c7bSclaudio 			free(cmd);
270cfc09c7bSclaudio 			break;
271cfc09c7bSclaudio 		}
272cfc09c7bSclaudio 		free(save);
273cfc09c7bSclaudio 	} else if (unveil(prog, "x") == -1)
274cfc09c7bSclaudio 		err(1, "%s: unveil", prog);
275cfc09c7bSclaudio 
276a0dad605Sclaudio 	if (pledge("stdio proc exec", NULL) == -1)
2776d83c0a3Sclaudio 		err(1, "pledge");
2786d83c0a3Sclaudio 
279cfc09c7bSclaudio 	/* Initialise retriever for children exiting. */
280cfc09c7bSclaudio 
281cfc09c7bSclaudio 	if (sigemptyset(&mask) == -1)
282cfc09c7bSclaudio 		err(1, NULL);
283cfc09c7bSclaudio 	if (signal(SIGCHLD, proc_child) == SIG_ERR)
284cfc09c7bSclaudio 		err(1, NULL);
285cfc09c7bSclaudio 	if (sigaddset(&mask, SIGCHLD) == -1)
286cfc09c7bSclaudio 		err(1, NULL);
287cfc09c7bSclaudio 	if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1)
288cfc09c7bSclaudio 		err(1, NULL);
289cfc09c7bSclaudio 
290cfc09c7bSclaudio 	for (;;) {
291b435c97dSclaudio 		char *uri, *dst, *compdst;
292b6884e9fSclaudio 		unsigned int id;
29395b65f7cSderaadt 		pid_t pid;
29495b65f7cSderaadt 		int st;
29595b65f7cSderaadt 
29636dac55eSclaudio 		pfd.events = 0;
29736dac55eSclaudio 		pfd.events |= POLLIN;
29825d36c5cSclaudio 		if (msgbuf_queuelen(msgq) > 0)
29908db1177Sclaudio 			pfd.events |= POLLOUT;
30008db1177Sclaudio 
3013e9f5857Sclaudio 		if (npending > 0 && nprocs < MAX_RSYNC_REQUESTS) {
3023e9f5857Sclaudio 			TAILQ_FOREACH(s, &states, entry) {
3033e9f5857Sclaudio 				if (s->pid == 0) {
3043e9f5857Sclaudio 					s->pid = exec_rsync(prog, bind_addr,
3053e9f5857Sclaudio 					    s->uri, s->dst, s->compdst);
3063e9f5857Sclaudio 					if (++nprocs >= MAX_RSYNC_REQUESTS)
3073e9f5857Sclaudio 						break;
3083e9f5857Sclaudio 					if (--npending == 0)
3093e9f5857Sclaudio 						break;
3103e9f5857Sclaudio 				}
3113e9f5857Sclaudio 			}
3123e9f5857Sclaudio 		}
3133e9f5857Sclaudio 
314cfc09c7bSclaudio 		if (ppoll(&pfd, 1, NULL, &oldmask) == -1) {
315cfc09c7bSclaudio 			if (errno != EINTR)
316cfc09c7bSclaudio 				err(1, "ppoll");
317cfc09c7bSclaudio 
318cfc09c7bSclaudio 			/*
319cfc09c7bSclaudio 			 * If we've received an EINTR, it means that one
320cfc09c7bSclaudio 			 * of our children has exited and we can reap it
321cfc09c7bSclaudio 			 * and look up its identifier.
322cfc09c7bSclaudio 			 * Then we respond to the parent.
323cfc09c7bSclaudio 			 */
324cfc09c7bSclaudio 
325cfc09c7bSclaudio 			while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) {
326cfc09c7bSclaudio 				int ok = 1;
327cfc09c7bSclaudio 
3283e9f5857Sclaudio 				TAILQ_FOREACH(s, &states, entry)
3293e9f5857Sclaudio 					if (s->pid == pid)
330cfc09c7bSclaudio 						break;
3313e9f5857Sclaudio 				if (s == NULL)
332b435c97dSclaudio 					errx(1, "waitpid: %d unexpected", pid);
333cfc09c7bSclaudio 
334cfc09c7bSclaudio 				if (!WIFEXITED(st)) {
335cfc09c7bSclaudio 					warnx("rsync %s terminated abnormally",
3363e9f5857Sclaudio 					    s->uri);
337cfc09c7bSclaudio 					rc = 1;
338cfc09c7bSclaudio 					ok = 0;
339cfc09c7bSclaudio 				} else if (WEXITSTATUS(st) != 0) {
3403e9f5857Sclaudio 					warnx("rsync %s failed", s->uri);
341cfc09c7bSclaudio 					ok = 0;
342cfc09c7bSclaudio 				}
343cfc09c7bSclaudio 
34425f7afeeSclaudio 				b = io_new_buffer();
3453e9f5857Sclaudio 				io_simple_buffer(b, &s->id, sizeof(s->id));
34608db1177Sclaudio 				io_simple_buffer(b, &ok, sizeof(ok));
34725d36c5cSclaudio 				io_close_buffer(msgq, b);
34826b5971fSclaudio 
3493e9f5857Sclaudio 				rsync_free(s);
35036dac55eSclaudio 				nprocs--;
351cfc09c7bSclaudio 			}
352cfc09c7bSclaudio 			if (pid == -1 && errno != ECHILD)
353cfc09c7bSclaudio 				err(1, "waitpid");
3543e9f5857Sclaudio 
355cfc09c7bSclaudio 			continue;
356cfc09c7bSclaudio 		}
357cfc09c7bSclaudio 
35808db1177Sclaudio 		if (pfd.revents & POLLOUT) {
35925d36c5cSclaudio 			if (msgbuf_write(fd, msgq) == -1) {
3609aadc625Sclaudio 				if (errno == EPIPE)
36108db1177Sclaudio 					errx(1, "write: connection closed");
3629aadc625Sclaudio 				else
36308db1177Sclaudio 					err(1, "write");
36408db1177Sclaudio 			}
36508db1177Sclaudio 		}
36608db1177Sclaudio 
3672defcb52Sclaudio 		/* connection closed */
3682defcb52Sclaudio 		if (pfd.revents & POLLHUP)
3692defcb52Sclaudio 			break;
3702defcb52Sclaudio 
37108db1177Sclaudio 		if (!(pfd.revents & POLLIN))
37208db1177Sclaudio 			continue;
37308db1177Sclaudio 
374*b5fa5d51Sclaudio 		switch (ibuf_read(fd, msgq)) {
375*b5fa5d51Sclaudio 		case -1:
376*b5fa5d51Sclaudio 			err(1, "ibuf_read");
377*b5fa5d51Sclaudio 		case 0:
378*b5fa5d51Sclaudio 			errx(1, "ibuf_read: connection closed");
379*b5fa5d51Sclaudio 		}
3807eb79a4aSclaudio 
381*b5fa5d51Sclaudio 		while ((b = io_buf_get(msgq)) != NULL) {
382cfc09c7bSclaudio 			/* Read host and module. */
3837eb79a4aSclaudio 			io_read_buf(b, &id, sizeof(id));
3847eb79a4aSclaudio 			io_read_str(b, &dst);
385b435c97dSclaudio 			io_read_str(b, &compdst);
3867eb79a4aSclaudio 			io_read_str(b, &uri);
3877eb79a4aSclaudio 
3887eb79a4aSclaudio 			ibuf_free(b);
3897eb79a4aSclaudio 
3903e9f5857Sclaudio 			if (dst != NULL) {
3913e9f5857Sclaudio 				rsync_new(id, uri, dst, compdst);
3923e9f5857Sclaudio 				npending++;
3933e9f5857Sclaudio 			} else {
3943e9f5857Sclaudio 				TAILQ_FOREACH(s, &states, entry)
3953e9f5857Sclaudio 					if (s->id == id)
396cfc09c7bSclaudio 						break;
3973e9f5857Sclaudio 				if (s != NULL) {
3983e9f5857Sclaudio 					if (s->pid != 0)
3993e9f5857Sclaudio 						kill(s->pid, SIGTERM);
4003e9f5857Sclaudio 					else
4013e9f5857Sclaudio 						rsync_free(s);
4023e9f5857Sclaudio 				}
4033e9f5857Sclaudio 			}
404cfc09c7bSclaudio 		}
405*b5fa5d51Sclaudio 	}
406cfc09c7bSclaudio 
407cfc09c7bSclaudio 	/* No need for these to be hanging around. */
4083e9f5857Sclaudio 	TAILQ_FOREACH_SAFE(s, &states, entry, ns) {
4093e9f5857Sclaudio 		if (s->pid != 0)
4103e9f5857Sclaudio 			kill(s->pid, SIGTERM);
4113e9f5857Sclaudio 		rsync_free(s);
412cfc09c7bSclaudio 	}
413cfc09c7bSclaudio 
41425d36c5cSclaudio 	msgbuf_free(msgq);
415cfc09c7bSclaudio 	exit(rc);
416cfc09c7bSclaudio }
417