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