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