Annotation of src/usr.bin/rsync/main.c, Revision 1.41
1.41 ! naddy 1: /* $Id: main.c,v 1.40 2019/03/30 23:48:24 schwarze 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: /*
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, ":/");
1.6 deraadt 44: return v[pos] == ':';
1.1 benno 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:
1.6 deraadt 56: if (strncasecmp(v, "rsync://", 8) == 0)
1.1 benno 57: return 1;
58:
59: pos = strcspn(v, ":/");
1.6 deraadt 60: return v[pos] == ':' && v[pos + 1] == ':';
1.1 benno 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 *
1.26 deraadt 74: fargs_parse(size_t argc, char *argv[], struct opts *opts)
1.1 benno 75: {
76: struct fargs *f = NULL;
1.26 deraadt 77: char *cp, *ccp;
1.1 benno 78: size_t i, j, len = 0;
79:
80: /* Allocations. */
81:
1.6 deraadt 82: if ((f = calloc(1, sizeof(struct fargs))) == NULL)
1.29 benno 83: err(1, "calloc");
1.1 benno 84:
85: f->sourcesz = argc - 1;
1.6 deraadt 86: if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
1.29 benno 87: err(1, "calloc");
1.1 benno 88:
89: for (i = 0; i < argc - 1; i++)
1.6 deraadt 90: if ((f->sources[i] = strdup(argv[i])) == NULL)
1.29 benno 91: err(1, "strdup");
1.1 benno 92:
1.6 deraadt 93: if ((f->sink = strdup(argv[i])) == NULL)
1.29 benno 94: err(1, "strdup");
1.1 benno 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;
1.6 deraadt 108: if ((f->host = strdup(f->sink)) == NULL)
1.29 benno 109: err(1, "strdup");
1.1 benno 110: }
111:
112: if (fargs_is_remote(f->sources[0])) {
1.6 deraadt 113: if (f->host != NULL)
1.35 deraadt 114: errx(1, "both source and destination cannot be remote files");
1.1 benno 115: f->mode = FARGS_RECEIVER;
1.6 deraadt 116: if ((f->host = strdup(f->sources[0])) == NULL)
1.29 benno 117: err(1, "strdup");
1.1 benno 118: }
119:
1.6 deraadt 120: if (f->host != NULL) {
121: if (strncasecmp(f->host, "rsync://", 8) == 0) {
1.30 benno 122: /* rsync://host[:port]/module[/path] */
1.1 benno 123: f->remote = 1;
124: len = strlen(f->host) - 8 + 1;
125: memmove(f->host, f->host + 8, len);
1.6 deraadt 126: if ((cp = strchr(f->host, '/')) == NULL)
1.35 deraadt 127: errx(1, "rsync protocol requires a module name");
1.1 benno 128: *cp++ = '\0';
129: f->module = cp;
1.6 deraadt 130: if ((cp = strchr(f->module, '/')) != NULL)
1.1 benno 131: *cp = '\0';
1.29 benno 132: if ((cp = strchr(f->host, ':'))) {
1.26 deraadt 133: /* host:port --> extract port */
134: *cp++ = '\0';
135: opts->port = cp;
136: }
1.1 benno 137: } else {
138: /* host:[/path] */
139: cp = strchr(f->host, ':');
1.6 deraadt 140: assert(cp != NULL);
1.1 benno 141: *cp++ = '\0';
1.6 deraadt 142: if (*cp == ':') {
1.1 benno 143: /* host::module[/path] */
144: f->remote = 1;
145: f->module = ++cp;
146: cp = strchr(f->module, '/');
1.6 deraadt 147: if (cp != NULL)
1.1 benno 148: *cp = '\0';
149: }
150: }
1.6 deraadt 151: if ((len = strlen(f->host)) == 0)
1.29 benno 152: errx(1, "empty remote host");
1.6 deraadt 153: if (f->remote && strlen(f->module) == 0)
1.29 benno 154: errx(1, "empty remote module");
1.1 benno 155: }
156:
157: /* Make sure we have the same "hostspec" for all files. */
158:
1.4 deraadt 159: if (!f->remote) {
1.6 deraadt 160: if (f->mode == FARGS_SENDER)
1.1 benno 161: for (i = 0; i < f->sourcesz; i++) {
1.4 deraadt 162: if (!fargs_is_remote(f->sources[i]))
1.1 benno 163: continue;
1.35 deraadt 164: errx(1,
165: "remote file in list of local sources: %s",
166: f->sources[i]);
1.1 benno 167: }
1.6 deraadt 168: if (f->mode == FARGS_RECEIVER)
1.1 benno 169: for (i = 0; i < f->sourcesz; i++) {
170: if (fargs_is_remote(f->sources[i]) &&
1.4 deraadt 171: !fargs_is_daemon(f->sources[i]))
1.1 benno 172: continue;
173: if (fargs_is_daemon(f->sources[i]))
1.35 deraadt 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]);
1.1 benno 179: }
180: } else {
1.6 deraadt 181: if (f->mode != FARGS_RECEIVER)
1.29 benno 182: errx(1, "sender mode for remote "
1.1 benno 183: "daemon receivers not yet supported");
184: for (i = 0; i < f->sourcesz; i++) {
185: if (fargs_is_daemon(f->sources[i]))
186: continue;
1.29 benno 187: errx(1, "non-remote daemon file "
1.1 benno 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:
1.4 deraadt 198: if (!f->remote) {
1.6 deraadt 199: if (f->host == NULL)
1.1 benno 200: return f;
1.6 deraadt 201: if (f->mode == FARGS_SENDER) {
202: assert(f->host != NULL);
1.1 benno 203: assert(len > 0);
204: j = strlen(f->sink);
205: memmove(f->sink, f->sink + len + 1, j - len);
206: return f;
1.6 deraadt 207: } else if (f->mode != FARGS_RECEIVER)
1.1 benno 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:
1.6 deraadt 219: assert(f->host != NULL);
1.1 benno 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 &&
1.6 deraadt 226: strncasecmp(cp, "rsync://", 8) == 0) {
1.1 benno 227: /* rsync://path */
228: cp += 8;
1.29 benno 229: if ((ccp = strchr(cp, ':'))) /* skip :port */
1.26 deraadt 230: *ccp = '\0';
1.1 benno 231: if (strncmp(cp, f->host, len) ||
1.11 benno 232: (cp[len] != '/' && cp[len] != '\0'))
1.35 deraadt 233: errx(1, "different remote host: %s",
234: f->sources[i]);
1.1 benno 235: memmove(f->sources[i],
236: f->sources[i] + len + 8 + 1,
237: j - len - 8);
1.6 deraadt 238: } else if (f->remote && strncmp(cp, "::", 2) == 0) {
1.1 benno 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) ||
1.6 deraadt 245: (cp[len] != ':' && cp[len] != '\0'))
1.35 deraadt 246: errx(1, "different remote host: %s",
247: f->sources[i]);
1.11 benno 248: memmove(f->sources[i], f->sources[i] + len + 2,
249: j - len - 1);
1.6 deraadt 250: } else if (cp[0] == ':') {
1.1 benno 251: /* :path */
252: memmove(f->sources[i], f->sources[i] + 1, j);
253: } else {
254: /* host:path */
255: if (strncmp(cp, f->host, len) ||
1.6 deraadt 256: (cp[len] != ':' && cp[len] != '\0'))
1.35 deraadt 257: errx(1, "different remote host: %s",
258: f->sources[i]);
1.1 benno 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;
1.2 benno 271: pid_t child;
1.41 ! naddy 272: int fds[2], sd, rc, c, st, i;
1.32 deraadt 273: struct sess sess;
1.1 benno 274: struct fargs *fargs;
1.32 deraadt 275: char **args;
1.1 benno 276: struct option lopts[] = {
1.26 deraadt 277: { "port", required_argument, NULL, 3 },
1.21 deraadt 278: { "rsh", required_argument, NULL, 'e' },
1.8 deraadt 279: { "rsync-path", required_argument, NULL, 1 },
280: { "sender", no_argument, &opts.sender, 1 },
281: { "server", no_argument, &opts.server, 1 },
1.20 deraadt 282: { "dry-run", no_argument, &opts.dry_run, 1 },
283: { "version", no_argument, NULL, 2 },
1.23 deraadt 284: { "archive", no_argument, NULL, 'a' },
1.21 deraadt 285: { "help", no_argument, NULL, 'h' },
1.36 deraadt 286: { "compress", no_argument, NULL, 'z' },
1.37 naddy 287: { "del", no_argument, &opts.del, 1 },
1.20 deraadt 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 },
1.8 deraadt 293: { "links", no_argument, &opts.preserve_links, 1 },
1.20 deraadt 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 },
1.8 deraadt 297: { "perms", no_argument, &opts.preserve_perms, 1 },
1.20 deraadt 298: { "no-perms", no_argument, &opts.preserve_perms, 0 },
1.31 benno 299: { "numeric-ids", no_argument, &opts.numeric_ids, 1 },
1.8 deraadt 300: { "recursive", no_argument, &opts.recursive, 1 },
1.20 deraadt 301: { "no-recursive", no_argument, &opts.recursive, 0 },
1.19 florian 302: { "specials", no_argument, &opts.specials, 1 },
1.21 deraadt 303: { "no-specials", no_argument, &opts.specials, 0 },
1.20 deraadt 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 },
1.8 deraadt 308: { NULL, 0, NULL, 0 }};
1.1 benno 309:
310: /* Global pledge. */
311:
1.19 florian 312: if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
1.6 deraadt 313: NULL) == -1)
1.29 benno 314: err(1, "pledge");
1.1 benno 315:
316: memset(&opts, 0, sizeof(struct opts));
317:
1.36 deraadt 318: while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvz", lopts, NULL))
1.30 benno 319: != -1) {
1.1 benno 320: switch (c) {
1.19 florian 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;
1.1 benno 335: case 'e':
1.9 deraadt 336: opts.ssh_prog = optarg;
1.1 benno 337: break;
1.10 benno 338: case 'g':
339: opts.preserve_gids = 1;
340: break;
1.1 benno 341: case 'l':
342: opts.preserve_links = 1;
343: break;
344: case 'n':
345: opts.dry_run = 1;
346: break;
1.13 florian 347: case 'o':
348: opts.preserve_uids = 1;
349: break;
1.1 benno 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;
1.36 deraadt 362: case 'z':
363: fprintf(stderr, "%s: -z not supported yet\n", getprogname());
364: break;
1.1 benno 365: case 0:
366: /* Non-NULL flag values (e.g., --sender). */
367: break;
368: case 1:
369: opts.rsync_path = optarg;
370: break;
1.17 deraadt 371: case 2:
372: fprintf(stderr, "openrsync: protocol version %u\n",
373: RSYNC_PROTOCOL);
374: exit(0);
1.26 deraadt 375: case 3:
376: opts.port = optarg;
377: break;
1.21 deraadt 378: case 'h':
1.1 benno 379: default:
380: goto usage;
381: }
382: }
383:
384: argc -= optind;
385: argv += optind;
386:
387: /* FIXME: reference implementation rsync accepts this. */
388:
389: if (argc < 2)
390: goto usage;
391:
1.26 deraadt 392: if (opts.port == NULL)
1.29 benno 393: opts.port = "rsync";
1.26 deraadt 394:
1.1 benno 395: /*
396: * This is what happens when we're started with the "hidden"
397: * --server option, which is invoked for the rsync on the remote
398: * host by the parent.
399: */
400:
1.32 deraadt 401: if (opts.server)
402: exit(rsync_server(&opts, (size_t)argc, argv));
1.1 benno 403:
404: /*
405: * Now we know that we're the client on the local machine
406: * invoking rsync(1).
407: * At this point, we need to start the client and server
408: * initiation logic.
409: * The client is what we continue running on this host; the
410: * server is what we'll use to connect to the remote and
411: * invoke rsync with the --server option.
412: */
413:
1.26 deraadt 414: fargs = fargs_parse(argc, argv, &opts);
1.6 deraadt 415: assert(fargs != NULL);
1.1 benno 416:
417: /*
418: * If we're contacting an rsync:// daemon, then we don't need to
419: * fork, because we won't start a server ourselves.
1.41 ! naddy 420: * Route directly into the socket code, unless a remote shell
! 421: * has explicitly been specified.
1.1 benno 422: */
423:
1.41 ! naddy 424: if (fargs->remote && opts.ssh_prog == NULL) {
1.6 deraadt 425: assert(fargs->mode == FARGS_RECEIVER);
1.41 ! naddy 426: if ((rc = rsync_connect(&opts, &sd, fargs)) == 0)
! 427: rc = rsync_socket(&opts, sd, fargs);
! 428: exit(rc);
1.1 benno 429: }
430:
431: /* Drop the dns/inet possibility. */
432:
1.19 florian 433: if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
1.6 deraadt 434: NULL) == -1)
1.29 benno 435: err(1, "pledge");
1.1 benno 436:
437: /* Create a bidirectional socket and start our child. */
438:
1.6 deraadt 439: if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
1.29 benno 440: err(1, "socketpair");
1.1 benno 441:
1.32 deraadt 442: switch ((child = fork())) {
443: case -1:
444: err(1, "fork");
445: case 0:
1.1 benno 446: close(fds[0]);
1.32 deraadt 447: if (pledge("stdio exec", NULL) == -1)
448: err(1, "pledge");
449:
450: memset(&sess, 0, sizeof(struct sess));
451: sess.opts = &opts;
1.1 benno 452:
1.41 ! naddy 453: if ((args = fargs_cmdline(&sess, fargs, NULL)) == NULL) {
1.32 deraadt 454: ERRX1(&sess, "fargs_cmdline");
455: _exit(1);
456: }
1.1 benno 457:
1.32 deraadt 458: for (i = 0; args[i] != NULL; i++)
459: LOG2(&sess, "exec[%d] = %s", i, args[i]);
1.1 benno 460:
1.32 deraadt 461: /* Make sure the child's stdin is from the sender. */
462: if (dup2(fds[1], STDIN_FILENO) == -1) {
463: ERR(&sess, "dup2");
464: _exit(1);
1.39 deraadt 465: }
466: if (dup2(fds[1], STDOUT_FILENO) == -1) {
1.32 deraadt 467: ERR(&sess, "dup2");
468: _exit(1);
469: }
470: execvp(args[0], args);
471: _exit(1);
1.1 benno 472: /* NOTREACHED */
1.32 deraadt 473: default:
474: close(fds[1]);
1.41 ! naddy 475: if (!fargs->remote)
! 476: rc = rsync_client(&opts, fds[0], fargs);
! 477: else
! 478: rc = rsync_socket(&opts, fds[0], fargs);
1.32 deraadt 479: break;
1.1 benno 480: }
481:
482: /*
483: * If the client has an error and exits, the server may be
484: * sitting around waiting to get data while we waitpid().
485: * So close the connection here so that they don't hang.
486: */
487:
1.34 deraadt 488: if (rc)
1.1 benno 489: close(fds[0]);
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.36 deraadt 507: fprintf(stderr,
1.40 schwarze 508: "usage: %s [-aDglnoprtv] [-e program] [--del] [--devices]\n"
509: "\t[--port=portnumber] [--rsync-path=program] [--specials]\n"
510: "\t[--version] source ... directory\n",
1.32 deraadt 511: getprogname());
512: exit(1);
1.1 benno 513: }