xref: /openbsd/usr.bin/rsync/main.c (revision 097a140d)
1 /*	$Id: main.c,v 1.53 2021/03/31 19:45:16 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 #include <sys/stat.h>
18 #include <sys/socket.h>
19 #include <sys/wait.h>
20 
21 #include <assert.h>
22 #include <err.h>
23 #include <getopt.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "extern.h"
31 
32 int verbose;
33 int poll_timeout;
34 
35 /*
36  * A remote host is has a colon before the first path separator.
37  * This works for rsh remote hosts (host:/foo/bar), implicit rsync
38  * remote hosts (host::/foo/bar), and explicit (rsync://host/foo).
39  * Return zero if local, non-zero if remote.
40  */
41 static int
42 fargs_is_remote(const char *v)
43 {
44 	size_t	 pos;
45 
46 	pos = strcspn(v, ":/");
47 	return v[pos] == ':';
48 }
49 
50 /*
51  * Test whether a remote host is specifically an rsync daemon.
52  * Return zero if not, non-zero if so.
53  */
54 static int
55 fargs_is_daemon(const char *v)
56 {
57 	size_t	 pos;
58 
59 	if (strncasecmp(v, "rsync://", 8) == 0)
60 		return 1;
61 
62 	pos = strcspn(v, ":/");
63 	return v[pos] == ':' && v[pos + 1] == ':';
64 }
65 
66 /*
67  * Take the command-line filenames (e.g., rsync foo/ bar/ baz/) and
68  * determine our operating mode.
69  * For example, if the first argument is a remote file, this means that
70  * we're going to transfer from the remote to the local.
71  * We also make sure that the arguments are consistent, that is, if
72  * we're going to transfer from the local to the remote, that no
73  * filenames for the local transfer indicate remote hosts.
74  * Always returns the parsed and sanitised options.
75  */
76 static struct fargs *
77 fargs_parse(size_t argc, char *argv[], struct opts *opts)
78 {
79 	struct fargs	*f = NULL;
80 	char		*cp, *ccp;
81 	size_t		 i, j, len = 0;
82 
83 	/* Allocations. */
84 
85 	if ((f = calloc(1, sizeof(struct fargs))) == NULL)
86 		err(1, "calloc");
87 
88 	f->sourcesz = argc - 1;
89 	if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
90 		err(1, "calloc");
91 
92 	for (i = 0; i < argc - 1; i++)
93 		if ((f->sources[i] = strdup(argv[i])) == NULL)
94 			err(1, "strdup");
95 
96 	if ((f->sink = strdup(argv[i])) == NULL)
97 		err(1, "strdup");
98 
99 	/*
100 	 * Test files for its locality.
101 	 * If the last is a remote host, then we're sending from the
102 	 * local to the remote host ("sender" mode).
103 	 * If the first, remote to local ("receiver" mode).
104 	 * If neither, a local transfer in sender style.
105 	 */
106 
107 	f->mode = FARGS_SENDER;
108 
109 	if (fargs_is_remote(f->sink)) {
110 		f->mode = FARGS_SENDER;
111 		if ((f->host = strdup(f->sink)) == NULL)
112 			err(1, "strdup");
113 	}
114 
115 	if (fargs_is_remote(f->sources[0])) {
116 		if (f->host != NULL)
117 			errx(1, "both source and destination cannot be remote files");
118 		f->mode = FARGS_RECEIVER;
119 		if ((f->host = strdup(f->sources[0])) == NULL)
120 			err(1, "strdup");
121 	}
122 
123 	if (f->host != NULL) {
124 		if (strncasecmp(f->host, "rsync://", 8) == 0) {
125 			/* rsync://host[:port]/module[/path] */
126 			f->remote = 1;
127 			len = strlen(f->host) - 8 + 1;
128 			memmove(f->host, f->host + 8, len);
129 			if ((cp = strchr(f->host, '/')) == NULL)
130 				errx(1, "rsync protocol requires a module name");
131 			*cp++ = '\0';
132 			f->module = cp;
133 			if ((cp = strchr(f->module, '/')) != NULL)
134 				*cp = '\0';
135 			if ((cp = strchr(f->host, ':')) != NULL) {
136 				/* host:port --> extract port */
137 				*cp++ = '\0';
138 				opts->port = cp;
139 			}
140 		} else {
141 			/* host:[/path] */
142 			cp = strchr(f->host, ':');
143 			assert(cp != NULL);
144 			*cp++ = '\0';
145 			if (*cp == ':') {
146 				/* host::module[/path] */
147 				f->remote = 1;
148 				f->module = ++cp;
149 				cp = strchr(f->module, '/');
150 				if (cp != NULL)
151 					*cp = '\0';
152 			}
153 		}
154 		if ((len = strlen(f->host)) == 0)
155 			errx(1, "empty remote host");
156 		if (f->remote && strlen(f->module) == 0)
157 			errx(1, "empty remote module");
158 	}
159 
160 	/* Make sure we have the same "hostspec" for all files. */
161 
162 	if (!f->remote) {
163 		if (f->mode == FARGS_SENDER)
164 			for (i = 0; i < f->sourcesz; i++) {
165 				if (!fargs_is_remote(f->sources[i]))
166 					continue;
167 				errx(1,
168 				    "remote file in list of local sources: %s",
169 				    f->sources[i]);
170 			}
171 		if (f->mode == FARGS_RECEIVER)
172 			for (i = 0; i < f->sourcesz; i++) {
173 				if (fargs_is_remote(f->sources[i]) &&
174 				    !fargs_is_daemon(f->sources[i]))
175 					continue;
176 				if (fargs_is_daemon(f->sources[i]))
177 					errx(1, "remote daemon in list of "
178 					    "remote sources: %s",
179 					    f->sources[i]);
180 				errx(1, "local file in list of remote sources: %s",
181 				    f->sources[i]);
182 			}
183 	} else {
184 		if (f->mode != FARGS_RECEIVER)
185 			errx(1, "sender mode for remote "
186 				"daemon receivers not yet supported");
187 		for (i = 0; i < f->sourcesz; i++) {
188 			if (fargs_is_daemon(f->sources[i]))
189 				continue;
190 			errx(1, "non-remote daemon file "
191 				"in list of remote daemon sources: "
192 				"%s", f->sources[i]);
193 		}
194 	}
195 
196 	/*
197 	 * If we're not remote and a sender, strip our hostname.
198 	 * Then exit if we're a sender or a local connection.
199 	 */
200 
201 	if (!f->remote) {
202 		if (f->host == NULL)
203 			return f;
204 		if (f->mode == FARGS_SENDER) {
205 			assert(f->host != NULL);
206 			assert(len > 0);
207 			j = strlen(f->sink);
208 			memmove(f->sink, f->sink + len + 1, j - len);
209 			return f;
210 		} else if (f->mode != FARGS_RECEIVER)
211 			return f;
212 	}
213 
214 	/*
215 	 * Now strip the hostnames from the remote host.
216 	 *   rsync://host/module/path -> module/path
217 	 *   host::module/path -> module/path
218 	 *   host:path -> path
219 	 * Also make sure that the remote hosts are the same.
220 	 */
221 
222 	assert(f->host != NULL);
223 	assert(len > 0);
224 
225 	for (i = 0; i < f->sourcesz; i++) {
226 		cp = f->sources[i];
227 		j = strlen(cp);
228 		if (f->remote &&
229 		    strncasecmp(cp, "rsync://", 8) == 0) {
230 			/* rsync://path */
231 			cp += 8;
232 			if ((ccp = strchr(cp, ':')))	/* skip :port */
233 				*ccp = '\0';
234 			if (strncmp(cp, f->host, len) ||
235 			    (cp[len] != '/' && cp[len] != '\0'))
236 				errx(1, "different remote host: %s",
237 				    f->sources[i]);
238 			memmove(f->sources[i],
239 				f->sources[i] + len + 8 + 1,
240 				j - len - 8);
241 		} else if (f->remote && strncmp(cp, "::", 2) == 0) {
242 			/* ::path */
243 			memmove(f->sources[i],
244 				f->sources[i] + 2, j - 1);
245 		} else if (f->remote) {
246 			/* host::path */
247 			if (strncmp(cp, f->host, len) ||
248 			    (cp[len] != ':' && cp[len] != '\0'))
249 				errx(1, "different remote host: %s",
250 				    f->sources[i]);
251 			memmove(f->sources[i], f->sources[i] + len + 2,
252 			    j - len - 1);
253 		} else if (cp[0] == ':') {
254 			/* :path */
255 			memmove(f->sources[i], f->sources[i] + 1, j);
256 		} else {
257 			/* host:path */
258 			if (strncmp(cp, f->host, len) ||
259 			    (cp[len] != ':' && cp[len] != '\0'))
260 				errx(1, "different remote host: %s",
261 				    f->sources[i]);
262 			memmove(f->sources[i],
263 				f->sources[i] + len + 1, j - len);
264 		}
265 	}
266 
267 	return f;
268 }
269 
270 int
271 main(int argc, char *argv[])
272 {
273 	struct opts	 opts;
274 	pid_t		 child;
275 	int		 fds[2], sd = -1, rc, c, st, i;
276 	struct sess	  sess;
277 	struct fargs	*fargs;
278 	char		**args;
279 	const char 	*errstr;
280 	const struct option	 lopts[] = {
281 		{ "port",	required_argument, NULL,		3 },
282 		{ "rsh",	required_argument, NULL,		'e' },
283 		{ "rsync-path",	required_argument, NULL,		1 },
284 		{ "sender",	no_argument,	&opts.sender,		1 },
285 		{ "server",	no_argument,	&opts.server,		1 },
286 		{ "dry-run",	no_argument,	&opts.dry_run,		1 },
287 		{ "version",	no_argument,	NULL,			2 },
288 		{ "archive",	no_argument,	NULL,			'a' },
289 		{ "help",	no_argument,	NULL,			'h' },
290 		{ "compress",	no_argument,	NULL,			'z' },
291 		{ "del",	no_argument,	&opts.del,		1 },
292 		{ "delete",	no_argument,	&opts.del,		1 },
293 		{ "devices",	no_argument,	&opts.devices,		1 },
294 		{ "no-devices",	no_argument,	&opts.devices,		0 },
295 		{ "group",	no_argument,	&opts.preserve_gids,	1 },
296 		{ "no-group",	no_argument,	&opts.preserve_gids,	0 },
297 		{ "links",	no_argument,	&opts.preserve_links,	1 },
298 		{ "no-links",	no_argument,	&opts.preserve_links,	0 },
299 		{ "owner",	no_argument,	&opts.preserve_uids,	1 },
300 		{ "no-owner",	no_argument,	&opts.preserve_uids,	0 },
301 		{ "perms",	no_argument,	&opts.preserve_perms,	1 },
302 		{ "no-perms",	no_argument,	&opts.preserve_perms,	0 },
303 		{ "numeric-ids", no_argument,	&opts.numeric_ids,	1 },
304 		{ "recursive",	no_argument,	&opts.recursive,	1 },
305 		{ "no-recursive", no_argument,	&opts.recursive,	0 },
306 		{ "specials",	no_argument,	&opts.specials,		1 },
307 		{ "no-specials", no_argument,	&opts.specials,		0 },
308 		{ "timeout",	required_argument, NULL,		5 },
309 		{ "times",	no_argument,	&opts.preserve_times,	1 },
310 		{ "no-times",	no_argument,	&opts.preserve_times,	0 },
311 		{ "verbose",	no_argument,	&verbose,		1 },
312 		{ "no-verbose",	no_argument,	&verbose,		0 },
313 		{ "address",	required_argument, NULL,		4 },
314 		{ "no-motd",	no_argument,	NULL,			6 },
315 		{ NULL,		0,		NULL,			0 }};
316 
317 	/* Global pledge. */
318 
319 	if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
320 	    NULL) == -1)
321 		err(1, "pledge");
322 
323 	memset(&opts, 0, sizeof(struct opts));
324 
325 	while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, NULL))
326 	    != -1) {
327 		switch (c) {
328 		case 'D':
329 			opts.devices = 1;
330 			opts.specials = 1;
331 			break;
332 		case 'a':
333 			opts.recursive = 1;
334 			opts.preserve_links = 1;
335 			opts.preserve_perms = 1;
336 			opts.preserve_times = 1;
337 			opts.preserve_gids = 1;
338 			opts.preserve_uids = 1;
339 			opts.devices = 1;
340 			opts.specials = 1;
341 			break;
342 		case 'e':
343 			opts.ssh_prog = optarg;
344 			break;
345 		case 'g':
346 			opts.preserve_gids = 1;
347 			break;
348 		case 'l':
349 			opts.preserve_links = 1;
350 			break;
351 		case 'n':
352 			opts.dry_run = 1;
353 			break;
354 		case 'o':
355 			opts.preserve_uids = 1;
356 			break;
357 		case 'p':
358 			opts.preserve_perms = 1;
359 			break;
360 		case 'r':
361 			opts.recursive = 1;
362 			break;
363 		case 't':
364 			opts.preserve_times = 1;
365 			break;
366 		case 'v':
367 			verbose++;
368 			break;
369 		case 'x':
370 			opts.one_file_system++;
371 			break;
372 		case 'z':
373 			fprintf(stderr, "%s: -z not supported yet\n", getprogname());
374 			break;
375 		case 0:
376 			/* Non-NULL flag values (e.g., --sender). */
377 			break;
378 		case 1:
379 			opts.rsync_path = optarg;
380 			break;
381 		case 2:
382 			fprintf(stderr, "openrsync: protocol version %u\n",
383 			    RSYNC_PROTOCOL);
384 			exit(0);
385 		case 3:
386 			opts.port = optarg;
387 			break;
388 		case 4:
389 			opts.address = optarg;
390 			break;
391 		case 5:
392 			poll_timeout = strtonum(optarg, 0, 60*60, &errstr);
393 			if (errstr != NULL)
394 				errx(1, "timeout is %s: %s", errstr, optarg);
395 			break;
396 		case 6:
397 			opts.no_motd = 1;
398 			break;
399 		case 'h':
400 		default:
401 			goto usage;
402 		}
403 	}
404 
405 	argc -= optind;
406 	argv += optind;
407 
408 	/* FIXME: reference implementation rsync accepts this. */
409 
410 	if (argc < 2)
411 		goto usage;
412 
413 	if (opts.port == NULL)
414 		opts.port = "rsync";
415 
416 	/* by default and for --timeout=0 disable poll_timeout */
417 	if (poll_timeout == 0)
418 		poll_timeout = -1;
419 	else
420 		poll_timeout *= 1000;
421 
422 	/*
423 	 * This is what happens when we're started with the "hidden"
424 	 * --server option, which is invoked for the rsync on the remote
425 	 * host by the parent.
426 	 */
427 
428 	if (opts.server)
429 		exit(rsync_server(&opts, (size_t)argc, argv));
430 
431 	/*
432 	 * Now we know that we're the client on the local machine
433 	 * invoking rsync(1).
434 	 * At this point, we need to start the client and server
435 	 * initiation logic.
436 	 * The client is what we continue running on this host; the
437 	 * server is what we'll use to connect to the remote and
438 	 * invoke rsync with the --server option.
439 	 */
440 
441 	fargs = fargs_parse(argc, argv, &opts);
442 	assert(fargs != NULL);
443 
444 	/*
445 	 * If we're contacting an rsync:// daemon, then we don't need to
446 	 * fork, because we won't start a server ourselves.
447 	 * Route directly into the socket code, unless a remote shell
448 	 * has explicitly been specified.
449 	 */
450 
451 	if (fargs->remote && opts.ssh_prog == NULL) {
452 		assert(fargs->mode == FARGS_RECEIVER);
453 		if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) {
454 			rc = rsync_socket(&opts, sd, fargs);
455 			close(sd);
456 		}
457 		exit(rc);
458 	}
459 
460 	/* Drop the dns/inet possibility. */
461 
462 	if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
463 	    NULL) == -1)
464 		err(1, "pledge");
465 
466 	/* Create a bidirectional socket and start our child. */
467 
468 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
469 		err(1, "socketpair");
470 
471 	switch ((child = fork())) {
472 	case -1:
473 		err(1, "fork");
474 	case 0:
475 		close(fds[0]);
476 		if (pledge("stdio exec", NULL) == -1)
477 			err(1, "pledge");
478 
479 		memset(&sess, 0, sizeof(struct sess));
480 		sess.opts = &opts;
481 
482 		if ((args = fargs_cmdline(&sess, fargs, NULL)) == NULL) {
483 			ERRX1("fargs_cmdline");
484 			_exit(1);
485 		}
486 
487 		for (i = 0; args[i] != NULL; i++)
488 			LOG2("exec[%d] = %s", i, args[i]);
489 
490 		/* Make sure the child's stdin is from the sender. */
491 		if (dup2(fds[1], STDIN_FILENO) == -1) {
492 			ERR("dup2");
493 			_exit(1);
494 		}
495 		if (dup2(fds[1], STDOUT_FILENO) == -1) {
496 			ERR("dup2");
497 			_exit(1);
498 		}
499 		execvp(args[0], args);
500 		_exit(1);
501 		/* NOTREACHED */
502 	default:
503 		close(fds[1]);
504 		if (!fargs->remote)
505 			rc = rsync_client(&opts, fds[0], fargs);
506 		else
507 			rc = rsync_socket(&opts, fds[0], fargs);
508 		break;
509 	}
510 
511 	close(fds[0]);
512 
513 	if (waitpid(child, &st, 0) == -1)
514 		err(1, "waitpid");
515 
516 	/*
517 	 * If we don't already have an error (rc == 0), then inherit the
518 	 * error code of rsync_server() if it has exited.
519 	 * If it hasn't exited, it overrides our return value.
520 	 */
521 
522 	if (WIFEXITED(st) && rc == 0)
523 		rc = WEXITSTATUS(st);
524 	else if (!WIFEXITED(st))
525 		rc = 1;
526 
527 	exit(rc);
528 usage:
529 	fprintf(stderr, "usage: %s"
530 	    " [-aDglnoprtvx] [-e program] [--address=sourceaddr] [--del]\n"
531 	    "\t[--no-motd] [--numeric-ids] [--port=portnumber] "
532 	    "[--rsync-path=program]\n\t[--timeout=seconds] [--version] "
533             "source ... directory\n",
534 	    getprogname());
535 	exit(1);
536 }
537