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