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