Annotation of src/usr.bin/rsync/main.c, Revision 1.51
1.51 ! claudio 1: /* $Id: main.c,v 1.50 2020/11/24 16:54:44 claudio Exp $ */
1.1 benno 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:
1.45 benno 32: int verbose;
1.50 claudio 33: int poll_timeout;
1.45 benno 34:
1.1 benno 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, ":/");
1.6 deraadt 47: return v[pos] == ':';
1.1 benno 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:
1.6 deraadt 59: if (strncasecmp(v, "rsync://", 8) == 0)
1.1 benno 60: return 1;
61:
62: pos = strcspn(v, ":/");
1.6 deraadt 63: return v[pos] == ':' && v[pos + 1] == ':';
1.1 benno 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 *
1.26 deraadt 77: fargs_parse(size_t argc, char *argv[], struct opts *opts)
1.1 benno 78: {
79: struct fargs *f = NULL;
1.26 deraadt 80: char *cp, *ccp;
1.1 benno 81: size_t i, j, len = 0;
82:
83: /* Allocations. */
84:
1.6 deraadt 85: if ((f = calloc(1, sizeof(struct fargs))) == NULL)
1.29 benno 86: err(1, "calloc");
1.1 benno 87:
88: f->sourcesz = argc - 1;
1.6 deraadt 89: if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
1.29 benno 90: err(1, "calloc");
1.1 benno 91:
92: for (i = 0; i < argc - 1; i++)
1.6 deraadt 93: if ((f->sources[i] = strdup(argv[i])) == NULL)
1.29 benno 94: err(1, "strdup");
1.1 benno 95:
1.6 deraadt 96: if ((f->sink = strdup(argv[i])) == NULL)
1.29 benno 97: err(1, "strdup");
1.1 benno 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;
1.6 deraadt 111: if ((f->host = strdup(f->sink)) == NULL)
1.29 benno 112: err(1, "strdup");
1.1 benno 113: }
114:
115: if (fargs_is_remote(f->sources[0])) {
1.6 deraadt 116: if (f->host != NULL)
1.35 deraadt 117: errx(1, "both source and destination cannot be remote files");
1.1 benno 118: f->mode = FARGS_RECEIVER;
1.6 deraadt 119: if ((f->host = strdup(f->sources[0])) == NULL)
1.29 benno 120: err(1, "strdup");
1.1 benno 121: }
122:
1.6 deraadt 123: if (f->host != NULL) {
124: if (strncasecmp(f->host, "rsync://", 8) == 0) {
1.30 benno 125: /* rsync://host[:port]/module[/path] */
1.1 benno 126: f->remote = 1;
127: len = strlen(f->host) - 8 + 1;
128: memmove(f->host, f->host + 8, len);
1.6 deraadt 129: if ((cp = strchr(f->host, '/')) == NULL)
1.35 deraadt 130: errx(1, "rsync protocol requires a module name");
1.1 benno 131: *cp++ = '\0';
132: f->module = cp;
1.6 deraadt 133: if ((cp = strchr(f->module, '/')) != NULL)
1.1 benno 134: *cp = '\0';
1.29 benno 135: if ((cp = strchr(f->host, ':'))) {
1.26 deraadt 136: /* host:port --> extract port */
137: *cp++ = '\0';
138: opts->port = cp;
139: }
1.1 benno 140: } else {
141: /* host:[/path] */
142: cp = strchr(f->host, ':');
1.6 deraadt 143: assert(cp != NULL);
1.1 benno 144: *cp++ = '\0';
1.6 deraadt 145: if (*cp == ':') {
1.1 benno 146: /* host::module[/path] */
147: f->remote = 1;
148: f->module = ++cp;
149: cp = strchr(f->module, '/');
1.6 deraadt 150: if (cp != NULL)
1.1 benno 151: *cp = '\0';
152: }
153: }
1.6 deraadt 154: if ((len = strlen(f->host)) == 0)
1.29 benno 155: errx(1, "empty remote host");
1.6 deraadt 156: if (f->remote && strlen(f->module) == 0)
1.29 benno 157: errx(1, "empty remote module");
1.1 benno 158: }
159:
160: /* Make sure we have the same "hostspec" for all files. */
161:
1.4 deraadt 162: if (!f->remote) {
1.6 deraadt 163: if (f->mode == FARGS_SENDER)
1.1 benno 164: for (i = 0; i < f->sourcesz; i++) {
1.4 deraadt 165: if (!fargs_is_remote(f->sources[i]))
1.1 benno 166: continue;
1.35 deraadt 167: errx(1,
168: "remote file in list of local sources: %s",
169: f->sources[i]);
1.1 benno 170: }
1.6 deraadt 171: if (f->mode == FARGS_RECEIVER)
1.1 benno 172: for (i = 0; i < f->sourcesz; i++) {
173: if (fargs_is_remote(f->sources[i]) &&
1.4 deraadt 174: !fargs_is_daemon(f->sources[i]))
1.1 benno 175: continue;
176: if (fargs_is_daemon(f->sources[i]))
1.35 deraadt 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]);
1.1 benno 182: }
183: } else {
1.6 deraadt 184: if (f->mode != FARGS_RECEIVER)
1.29 benno 185: errx(1, "sender mode for remote "
1.1 benno 186: "daemon receivers not yet supported");
187: for (i = 0; i < f->sourcesz; i++) {
188: if (fargs_is_daemon(f->sources[i]))
189: continue;
1.29 benno 190: errx(1, "non-remote daemon file "
1.1 benno 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:
1.4 deraadt 201: if (!f->remote) {
1.6 deraadt 202: if (f->host == NULL)
1.1 benno 203: return f;
1.6 deraadt 204: if (f->mode == FARGS_SENDER) {
205: assert(f->host != NULL);
1.1 benno 206: assert(len > 0);
207: j = strlen(f->sink);
208: memmove(f->sink, f->sink + len + 1, j - len);
209: return f;
1.6 deraadt 210: } else if (f->mode != FARGS_RECEIVER)
1.1 benno 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:
1.6 deraadt 222: assert(f->host != NULL);
1.1 benno 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 &&
1.6 deraadt 229: strncasecmp(cp, "rsync://", 8) == 0) {
1.1 benno 230: /* rsync://path */
231: cp += 8;
1.29 benno 232: if ((ccp = strchr(cp, ':'))) /* skip :port */
1.26 deraadt 233: *ccp = '\0';
1.1 benno 234: if (strncmp(cp, f->host, len) ||
1.11 benno 235: (cp[len] != '/' && cp[len] != '\0'))
1.35 deraadt 236: errx(1, "different remote host: %s",
237: f->sources[i]);
1.1 benno 238: memmove(f->sources[i],
239: f->sources[i] + len + 8 + 1,
240: j - len - 8);
1.6 deraadt 241: } else if (f->remote && strncmp(cp, "::", 2) == 0) {
1.1 benno 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) ||
1.6 deraadt 248: (cp[len] != ':' && cp[len] != '\0'))
1.35 deraadt 249: errx(1, "different remote host: %s",
250: f->sources[i]);
1.11 benno 251: memmove(f->sources[i], f->sources[i] + len + 2,
252: j - len - 1);
1.6 deraadt 253: } else if (cp[0] == ':') {
1.1 benno 254: /* :path */
255: memmove(f->sources[i], f->sources[i] + 1, j);
256: } else {
257: /* host:path */
258: if (strncmp(cp, f->host, len) ||
1.6 deraadt 259: (cp[len] != ':' && cp[len] != '\0'))
1.35 deraadt 260: errx(1, "different remote host: %s",
261: f->sources[i]);
1.1 benno 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;
1.2 benno 274: pid_t child;
1.46 deraadt 275: int fds[2], sd = -1, rc, c, st, i;
1.32 deraadt 276: struct sess sess;
1.1 benno 277: struct fargs *fargs;
1.32 deraadt 278: char **args;
1.50 claudio 279: const char *errstr;
280: const struct option lopts[] = {
1.26 deraadt 281: { "port", required_argument, NULL, 3 },
1.21 deraadt 282: { "rsh", required_argument, NULL, 'e' },
1.8 deraadt 283: { "rsync-path", required_argument, NULL, 1 },
284: { "sender", no_argument, &opts.sender, 1 },
285: { "server", no_argument, &opts.server, 1 },
1.20 deraadt 286: { "dry-run", no_argument, &opts.dry_run, 1 },
287: { "version", no_argument, NULL, 2 },
1.23 deraadt 288: { "archive", no_argument, NULL, 'a' },
1.21 deraadt 289: { "help", no_argument, NULL, 'h' },
1.36 deraadt 290: { "compress", no_argument, NULL, 'z' },
1.37 naddy 291: { "del", no_argument, &opts.del, 1 },
1.20 deraadt 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 },
1.8 deraadt 297: { "links", no_argument, &opts.preserve_links, 1 },
1.20 deraadt 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 },
1.8 deraadt 301: { "perms", no_argument, &opts.preserve_perms, 1 },
1.20 deraadt 302: { "no-perms", no_argument, &opts.preserve_perms, 0 },
1.31 benno 303: { "numeric-ids", no_argument, &opts.numeric_ids, 1 },
1.8 deraadt 304: { "recursive", no_argument, &opts.recursive, 1 },
1.20 deraadt 305: { "no-recursive", no_argument, &opts.recursive, 0 },
1.19 florian 306: { "specials", no_argument, &opts.specials, 1 },
1.21 deraadt 307: { "no-specials", no_argument, &opts.specials, 0 },
1.50 claudio 308: { "timeout", required_argument, NULL, 5 },
1.20 deraadt 309: { "times", no_argument, &opts.preserve_times, 1 },
310: { "no-times", no_argument, &opts.preserve_times, 0 },
1.45 benno 311: { "verbose", no_argument, &verbose, 1 },
312: { "no-verbose", no_argument, &verbose, 0 },
1.48 claudio 313: { "address", required_argument, NULL, 4 },
1.8 deraadt 314: { NULL, 0, NULL, 0 }};
1.1 benno 315:
316: /* Global pledge. */
317:
1.19 florian 318: if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
1.6 deraadt 319: NULL) == -1)
1.29 benno 320: err(1, "pledge");
1.1 benno 321:
322: memset(&opts, 0, sizeof(struct opts));
323:
1.44 bket 324: while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, NULL))
1.30 benno 325: != -1) {
1.1 benno 326: switch (c) {
1.19 florian 327: case 'D':
328: opts.devices = 1;
329: opts.specials = 1;
330: break;
331: case 'a':
332: opts.recursive = 1;
333: opts.preserve_links = 1;
334: opts.preserve_perms = 1;
335: opts.preserve_times = 1;
336: opts.preserve_gids = 1;
337: opts.preserve_uids = 1;
338: opts.devices = 1;
339: opts.specials = 1;
340: break;
1.1 benno 341: case 'e':
1.9 deraadt 342: opts.ssh_prog = optarg;
1.1 benno 343: break;
1.10 benno 344: case 'g':
345: opts.preserve_gids = 1;
346: break;
1.1 benno 347: case 'l':
348: opts.preserve_links = 1;
349: break;
350: case 'n':
351: opts.dry_run = 1;
352: break;
1.13 florian 353: case 'o':
354: opts.preserve_uids = 1;
355: break;
1.1 benno 356: case 'p':
357: opts.preserve_perms = 1;
358: break;
359: case 'r':
360: opts.recursive = 1;
361: break;
362: case 't':
363: opts.preserve_times = 1;
364: break;
365: case 'v':
1.45 benno 366: verbose++;
1.1 benno 367: break;
1.44 bket 368: case 'x':
369: opts.one_file_system++;
370: break;
1.36 deraadt 371: case 'z':
372: fprintf(stderr, "%s: -z not supported yet\n", getprogname());
373: break;
1.1 benno 374: case 0:
375: /* Non-NULL flag values (e.g., --sender). */
376: break;
377: case 1:
378: opts.rsync_path = optarg;
379: break;
1.17 deraadt 380: case 2:
381: fprintf(stderr, "openrsync: protocol version %u\n",
382: RSYNC_PROTOCOL);
383: exit(0);
1.26 deraadt 384: case 3:
385: opts.port = optarg;
386: break;
1.48 claudio 387: case 4:
388: opts.address = optarg;
389: break;
1.50 claudio 390: case 5:
391: poll_timeout = strtonum(optarg, 0, 60*60, &errstr);
392: if (errstr != NULL)
393: errx(1, "timeout is %s: %s", errstr, optarg);
394: break;
1.21 deraadt 395: case 'h':
1.1 benno 396: default:
397: goto usage;
398: }
399: }
400:
401: argc -= optind;
402: argv += optind;
403:
404: /* FIXME: reference implementation rsync accepts this. */
405:
406: if (argc < 2)
407: goto usage;
408:
1.26 deraadt 409: if (opts.port == NULL)
1.29 benno 410: opts.port = "rsync";
1.26 deraadt 411:
1.51 ! claudio 412: /* by default and for --timeout=0 disable poll_timeout */
! 413: if (poll_timeout == 0)
! 414: poll_timeout = -1;
! 415: else
! 416: poll_timeout *= 1000;
! 417:
1.1 benno 418: /*
419: * This is what happens when we're started with the "hidden"
420: * --server option, which is invoked for the rsync on the remote
421: * host by the parent.
422: */
423:
1.32 deraadt 424: if (opts.server)
425: exit(rsync_server(&opts, (size_t)argc, argv));
1.50 claudio 426:
1.1 benno 427: /*
428: * Now we know that we're the client on the local machine
429: * invoking rsync(1).
430: * At this point, we need to start the client and server
431: * initiation logic.
432: * The client is what we continue running on this host; the
433: * server is what we'll use to connect to the remote and
434: * invoke rsync with the --server option.
435: */
436:
1.26 deraadt 437: fargs = fargs_parse(argc, argv, &opts);
1.6 deraadt 438: assert(fargs != NULL);
1.1 benno 439:
440: /*
441: * If we're contacting an rsync:// daemon, then we don't need to
442: * fork, because we won't start a server ourselves.
1.41 naddy 443: * Route directly into the socket code, unless a remote shell
444: * has explicitly been specified.
1.1 benno 445: */
446:
1.41 naddy 447: if (fargs->remote && opts.ssh_prog == NULL) {
1.6 deraadt 448: assert(fargs->mode == FARGS_RECEIVER);
1.47 naddy 449: if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) {
1.41 naddy 450: rc = rsync_socket(&opts, sd, fargs);
1.47 naddy 451: close(sd);
452: }
1.41 naddy 453: exit(rc);
1.1 benno 454: }
455:
456: /* Drop the dns/inet possibility. */
457:
1.19 florian 458: if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
1.6 deraadt 459: NULL) == -1)
1.29 benno 460: err(1, "pledge");
1.1 benno 461:
462: /* Create a bidirectional socket and start our child. */
463:
1.6 deraadt 464: if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
1.29 benno 465: err(1, "socketpair");
1.1 benno 466:
1.32 deraadt 467: switch ((child = fork())) {
468: case -1:
469: err(1, "fork");
470: case 0:
1.1 benno 471: close(fds[0]);
1.32 deraadt 472: if (pledge("stdio exec", NULL) == -1)
473: err(1, "pledge");
474:
475: memset(&sess, 0, sizeof(struct sess));
476: sess.opts = &opts;
1.1 benno 477:
1.41 naddy 478: if ((args = fargs_cmdline(&sess, fargs, NULL)) == NULL) {
1.45 benno 479: ERRX1("fargs_cmdline");
1.32 deraadt 480: _exit(1);
481: }
1.1 benno 482:
1.32 deraadt 483: for (i = 0; args[i] != NULL; i++)
1.45 benno 484: LOG2("exec[%d] = %s", i, args[i]);
1.1 benno 485:
1.32 deraadt 486: /* Make sure the child's stdin is from the sender. */
487: if (dup2(fds[1], STDIN_FILENO) == -1) {
1.45 benno 488: ERR("dup2");
1.32 deraadt 489: _exit(1);
1.39 deraadt 490: }
491: if (dup2(fds[1], STDOUT_FILENO) == -1) {
1.45 benno 492: ERR("dup2");
1.32 deraadt 493: _exit(1);
494: }
495: execvp(args[0], args);
496: _exit(1);
1.1 benno 497: /* NOTREACHED */
1.32 deraadt 498: default:
499: close(fds[1]);
1.41 naddy 500: if (!fargs->remote)
501: rc = rsync_client(&opts, fds[0], fargs);
502: else
503: rc = rsync_socket(&opts, fds[0], fargs);
1.32 deraadt 504: break;
1.1 benno 505: }
506:
1.47 naddy 507: close(fds[0]);
1.1 benno 508:
1.6 deraadt 509: if (waitpid(child, &st, 0) == -1)
1.29 benno 510: err(1, "waitpid");
1.34 deraadt 511:
512: /*
513: * If we don't already have an error (rc == 0), then inherit the
514: * error code of rsync_server() if it has exited.
515: * If it hasn't exited, it overrides our return value.
516: */
517:
518: if (WIFEXITED(st) && rc == 0)
519: rc = WEXITSTATUS(st);
520: else if (!WIFEXITED(st))
521: rc = 1;
522:
1.32 deraadt 523: exit(rc);
1.1 benno 524: usage:
1.42 schwarze 525: fprintf(stderr, "usage: %s"
1.49 deraadt 526: " [-aDglnoprtvx] [-e program] [--address=sourceaddr] [--del]\n"
1.48 claudio 527: "\t[--numeric-ids] [--port=portnumber] [--rsync-path=program]\n"
1.50 claudio 528: "\t[--timeout=seconds] [--version] source ... directory\n",
1.32 deraadt 529: getprogname());
530: exit(1);
1.1 benno 531: }