Annotation of src/usr.bin/rsync/main.c, Revision 1.8
1.8 ! deraadt 1: /* $Id: main.c,v 1.7 2019/02/11 21:44:44 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:
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 *
92: fargs_parse(size_t argc, char *argv[])
93: {
94: struct fargs *f = NULL;
95: char *cp;
96: size_t i, j, len = 0;
97:
98: /* Allocations. */
99:
1.6 deraadt 100: if ((f = calloc(1, sizeof(struct fargs))) == NULL)
1.1 benno 101: err(EXIT_FAILURE, "calloc");
102:
103: f->sourcesz = argc - 1;
1.6 deraadt 104: if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
1.1 benno 105: err(EXIT_FAILURE, "calloc");
106:
107: for (i = 0; i < argc - 1; i++)
1.6 deraadt 108: if ((f->sources[i] = strdup(argv[i])) == NULL)
1.1 benno 109: err(EXIT_FAILURE, "strdup");
110:
1.6 deraadt 111: if ((f->sink = strdup(argv[i])) == NULL)
1.1 benno 112: err(EXIT_FAILURE, "strdup");
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.1 benno 127: err(EXIT_FAILURE, "strdup");
128: }
129:
130: if (fargs_is_remote(f->sources[0])) {
1.6 deraadt 131: if (f->host != NULL)
1.1 benno 132: errx(EXIT_FAILURE, "both source and "
133: "destination cannot be remote files");
134: f->mode = FARGS_RECEIVER;
1.6 deraadt 135: if ((f->host = strdup(f->sources[0])) == NULL)
1.1 benno 136: err(EXIT_FAILURE, "strdup");
137: }
138:
1.6 deraadt 139: if (f->host != NULL) {
140: if (strncasecmp(f->host, "rsync://", 8) == 0) {
1.1 benno 141: /* rsync://host/module[/path] */
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.1 benno 146: errx(EXIT_FAILURE, "rsync protocol "
147: "requires a module name");
148: *cp++ = '\0';
149: f->module = cp;
1.6 deraadt 150: if ((cp = strchr(f->module, '/')) != NULL)
1.1 benno 151: *cp = '\0';
152: } else {
153: /* host:[/path] */
154: cp = strchr(f->host, ':');
1.6 deraadt 155: assert(cp != NULL);
1.1 benno 156: *cp++ = '\0';
1.6 deraadt 157: if (*cp == ':') {
1.1 benno 158: /* host::module[/path] */
159: f->remote = 1;
160: f->module = ++cp;
161: cp = strchr(f->module, '/');
1.6 deraadt 162: if (cp != NULL)
1.1 benno 163: *cp = '\0';
164: }
165: }
1.6 deraadt 166: if ((len = strlen(f->host)) == 0)
1.1 benno 167: errx(EXIT_FAILURE, "empty remote host");
1.6 deraadt 168: if (f->remote && strlen(f->module) == 0)
1.1 benno 169: errx(EXIT_FAILURE, "empty remote module");
170: }
171:
172: /* Make sure we have the same "hostspec" for all files. */
173:
1.4 deraadt 174: if (!f->remote) {
1.6 deraadt 175: if (f->mode == FARGS_SENDER)
1.1 benno 176: for (i = 0; i < f->sourcesz; i++) {
1.4 deraadt 177: if (!fargs_is_remote(f->sources[i]))
1.1 benno 178: continue;
179: errx(EXIT_FAILURE, "remote file in "
180: "list of local sources: %s",
181: f->sources[i]);
182: }
1.6 deraadt 183: if (f->mode == FARGS_RECEIVER)
1.1 benno 184: for (i = 0; i < f->sourcesz; i++) {
185: if (fargs_is_remote(f->sources[i]) &&
1.4 deraadt 186: !fargs_is_daemon(f->sources[i]))
1.1 benno 187: continue;
188: if (fargs_is_daemon(f->sources[i]))
189: errx(EXIT_FAILURE, "remote "
190: "daemon in list of "
191: "remote sources: %s",
192: f->sources[i]);
193: errx(EXIT_FAILURE, "local file in "
194: "list of remote sources: %s",
195: f->sources[i]);
196: }
197: } else {
1.6 deraadt 198: if (f->mode != FARGS_RECEIVER)
1.1 benno 199: errx(EXIT_FAILURE, "sender mode for remote "
200: "daemon receivers not yet supported");
201: for (i = 0; i < f->sourcesz; i++) {
202: if (fargs_is_daemon(f->sources[i]))
203: continue;
204: errx(EXIT_FAILURE, "non-remote daemon file "
205: "in list of remote daemon sources: "
206: "%s", f->sources[i]);
207: }
208: }
209:
210: /*
211: * If we're not remote and a sender, strip our hostname.
212: * Then exit if we're a sender or a local connection.
213: */
214:
1.4 deraadt 215: if (!f->remote) {
1.6 deraadt 216: if (f->host == NULL)
1.1 benno 217: return f;
1.6 deraadt 218: if (f->mode == FARGS_SENDER) {
219: assert(f->host != NULL);
1.1 benno 220: assert(len > 0);
221: j = strlen(f->sink);
222: memmove(f->sink, f->sink + len + 1, j - len);
223: return f;
1.6 deraadt 224: } else if (f->mode != FARGS_RECEIVER)
1.1 benno 225: return f;
226: }
227:
228: /*
229: * Now strip the hostnames from the remote host.
230: * rsync://host/module/path -> module/path
231: * host::module/path -> module/path
232: * host:path -> path
233: * Also make sure that the remote hosts are the same.
234: */
235:
1.6 deraadt 236: assert(f->host != NULL);
1.1 benno 237: assert(len > 0);
238:
239: for (i = 0; i < f->sourcesz; i++) {
240: cp = f->sources[i];
241: j = strlen(cp);
242: if (f->remote &&
1.6 deraadt 243: strncasecmp(cp, "rsync://", 8) == 0) {
1.1 benno 244: /* rsync://path */
245: cp += 8;
246: if (strncmp(cp, f->host, len) ||
1.6 deraadt 247: (cp[len] != '/' && cp[len] != '\0'))
1.1 benno 248: errx(EXIT_FAILURE, "different remote "
249: "host: %s", f->sources[i]);
250: memmove(f->sources[i],
251: f->sources[i] + len + 8 + 1,
252: j - len - 8);
1.6 deraadt 253: } else if (f->remote && strncmp(cp, "::", 2) == 0) {
1.1 benno 254: /* ::path */
255: memmove(f->sources[i],
256: f->sources[i] + 2, j - 1);
257: } else if (f->remote) {
258: /* host::path */
259: if (strncmp(cp, f->host, len) ||
1.6 deraadt 260: (cp[len] != ':' && cp[len] != '\0'))
1.1 benno 261: errx(EXIT_FAILURE, "different remote "
262: "host: %s", f->sources[i]);
1.6 deraadt 263: memmove(f->sources[i], f->sources[i] + len + 2, j - len - 1);
264: } else if (cp[0] == ':') {
1.1 benno 265: /* :path */
266: memmove(f->sources[i], f->sources[i] + 1, j);
267: } else {
268: /* host:path */
269: if (strncmp(cp, f->host, len) ||
1.6 deraadt 270: (cp[len] != ':' && cp[len] != '\0'))
1.1 benno 271: errx(EXIT_FAILURE, "different remote "
272: "host: %s", f->sources[i]);
273: memmove(f->sources[i],
274: f->sources[i] + len + 1, j - len);
275: }
276: }
277:
278: return f;
279: }
280:
281: int
282: main(int argc, char *argv[])
283: {
284: struct opts opts;
1.2 benno 285: pid_t child;
1.5 deraadt 286: int fds[2], c, st;
1.1 benno 287: struct fargs *fargs;
288: struct option lopts[] = {
1.8 ! deraadt 289: { "delete", no_argument, &opts.del, 1 },
! 290: { "rsync-path", required_argument, NULL, 1 },
! 291: { "sender", no_argument, &opts.sender, 1 },
! 292: { "server", no_argument, &opts.server, 1 },
! 293: { "verbose", no_argument, &opts.verbose, 1 },
! 294: { "links", no_argument, &opts.preserve_links, 1 },
! 295: { "dry-run", no_argument, &opts.dry_run, 1 },
! 296: { "perms", no_argument, &opts.preserve_perms, 1 },
! 297: { "recursive", no_argument, &opts.recursive, 1 },
! 298: { "times", no_argument, &opts.preserve_times, 1 },
! 299: { NULL, 0, NULL, 0 }};
1.1 benno 300:
301: /* Global pledge. */
302:
1.6 deraadt 303: if (pledge("stdio rpath wpath cpath inet fattr dns proc exec unveil",
304: NULL) == -1)
1.1 benno 305: err(EXIT_FAILURE, "pledge");
306:
307: memset(&opts, 0, sizeof(struct opts));
308:
1.6 deraadt 309: while ((c = getopt_long(argc, argv, "e:lnprtv", lopts, NULL)) != -1) {
1.1 benno 310: switch (c) {
311: case 'e':
312: /* Ignore. */
313: break;
314: case 'l':
315: opts.preserve_links = 1;
316: break;
317: case 'n':
318: opts.dry_run = 1;
319: break;
320: case 'p':
321: opts.preserve_perms = 1;
322: break;
323: case 'r':
324: opts.recursive = 1;
325: break;
326: case 't':
327: opts.preserve_times = 1;
328: break;
329: case 'v':
330: opts.verbose++;
331: break;
332: case 0:
333: /* Non-NULL flag values (e.g., --sender). */
334: break;
335: case 1:
336: opts.rsync_path = optarg;
337: break;
338: default:
339: goto usage;
340: }
341: }
342:
343: argc -= optind;
344: argv += optind;
345:
346: /* FIXME: reference implementation rsync accepts this. */
347:
348: if (argc < 2)
349: goto usage;
350:
351: /*
352: * This is what happens when we're started with the "hidden"
353: * --server option, which is invoked for the rsync on the remote
354: * host by the parent.
355: */
356:
357: if (opts.server) {
1.6 deraadt 358: if (pledge("stdio rpath wpath cpath fattr unveil", NULL) == -1)
1.1 benno 359: err(EXIT_FAILURE, "pledge");
360: c = rsync_server(&opts, (size_t)argc, argv);
361: return c ? EXIT_SUCCESS : EXIT_FAILURE;
362: }
363:
364: /*
365: * Now we know that we're the client on the local machine
366: * invoking rsync(1).
367: * At this point, we need to start the client and server
368: * initiation logic.
369: * The client is what we continue running on this host; the
370: * server is what we'll use to connect to the remote and
371: * invoke rsync with the --server option.
372: */
373:
374: fargs = fargs_parse(argc, argv);
1.6 deraadt 375: assert(fargs != NULL);
1.1 benno 376:
377: /*
378: * If we're contacting an rsync:// daemon, then we don't need to
379: * fork, because we won't start a server ourselves.
380: * Route directly into the socket code, in that case.
381: */
382:
383: if (fargs->remote) {
1.6 deraadt 384: assert(fargs->mode == FARGS_RECEIVER);
385: if (pledge("stdio rpath wpath cpath inet fattr dns unveil",
386: NULL) == -1)
1.1 benno 387: err(EXIT_FAILURE, "pledge");
388: c = rsync_socket(&opts, fargs);
389: fargs_free(fargs);
390: return c ? EXIT_SUCCESS : EXIT_FAILURE;
391: }
392:
393: /* Drop the dns/inet possibility. */
394:
1.6 deraadt 395: if (pledge("stdio rpath wpath cpath fattr proc exec unveil",
396: NULL) == -1)
1.1 benno 397: err(EXIT_FAILURE, "pledge");
398:
399: /* Create a bidirectional socket and start our child. */
400:
1.6 deraadt 401: if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
1.1 benno 402: err(EXIT_FAILURE, "socketpair");
403:
1.6 deraadt 404: if ((child = fork()) == -1) {
1.1 benno 405: close(fds[0]);
406: close(fds[1]);
407: err(EXIT_FAILURE, "fork");
408: }
409:
410: /* Drop the fork possibility. */
411:
1.6 deraadt 412: if (pledge("stdio rpath wpath cpath fattr exec unveil", NULL) == -1)
1.1 benno 413: err(EXIT_FAILURE, "pledge");
414:
1.6 deraadt 415: if (child == 0) {
1.1 benno 416: close(fds[0]);
417: fds[0] = -1;
1.6 deraadt 418: if (pledge("stdio exec", NULL) == -1)
1.1 benno 419: err(EXIT_FAILURE, "pledge");
420: rsync_child(&opts, fds[1], fargs);
421: /* NOTREACHED */
422: }
423:
424: close(fds[1]);
425: fds[1] = -1;
1.6 deraadt 426: if (pledge("stdio rpath wpath cpath fattr unveil", NULL) == -1)
1.1 benno 427: err(EXIT_FAILURE, "pledge");
428: c = rsync_client(&opts, fds[0], fargs);
429: fargs_free(fargs);
430:
431: /*
432: * If the client has an error and exits, the server may be
433: * sitting around waiting to get data while we waitpid().
434: * So close the connection here so that they don't hang.
435: */
436:
1.4 deraadt 437: if (!c) {
1.1 benno 438: close(fds[0]);
439: fds[0] = -1;
440: }
441:
1.6 deraadt 442: if (waitpid(child, &st, 0) == -1)
1.1 benno 443: err(EXIT_FAILURE, "waitpid");
1.6 deraadt 444: if (!(WIFEXITED(st) && WEXITSTATUS(st) == EXIT_SUCCESS))
1.1 benno 445: c = 0;
446:
1.6 deraadt 447: if (fds[0] != -1)
1.1 benno 448: close(fds[0]);
449: return c ? EXIT_SUCCESS : EXIT_FAILURE;
450: usage:
451: fprintf(stderr, "usage: %s [-lnprtv] "
452: "[--delete] [--rsync-path=prog] src ... dst\n",
453: getprogname());
454: return EXIT_FAILURE;
455: }