1 /* $OpenBSD: main.c,v 1.72 2025/01/16 14:06:49 job Exp $ */
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 #include <util.h>
30
31 #include "extern.h"
32 #include "version.h"
33
34 int verbose;
35 int poll_contimeout;
36 int poll_timeout;
37
38 /*
39 * A remote host is has a colon before the first path separator.
40 * This works for rsh remote hosts (host:/foo/bar), implicit rsync
41 * remote hosts (host::/foo/bar), and explicit (rsync://host/foo).
42 * Return zero if local, non-zero if remote.
43 */
44 static int
fargs_is_remote(const char * v)45 fargs_is_remote(const char *v)
46 {
47 size_t pos;
48
49 pos = strcspn(v, ":/");
50 return v[pos] == ':';
51 }
52
53 /*
54 * Test whether a remote host is specifically an rsync daemon.
55 * Return zero if not, non-zero if so.
56 */
57 static int
fargs_is_daemon(const char * v)58 fargs_is_daemon(const char *v)
59 {
60 size_t pos;
61
62 if (strncasecmp(v, "rsync://", 8) == 0)
63 return 1;
64
65 pos = strcspn(v, ":/");
66 return v[pos] == ':' && v[pos + 1] == ':';
67 }
68
69 /*
70 * Take the command-line filenames (e.g., rsync foo/ bar/ baz/) and
71 * determine our operating mode.
72 * For example, if the first argument is a remote file, this means that
73 * we're going to transfer from the remote to the local.
74 * We also make sure that the arguments are consistent, that is, if
75 * we're going to transfer from the local to the remote, that no
76 * filenames for the local transfer indicate remote hosts.
77 * Always returns the parsed and sanitised options.
78 */
79 static struct fargs *
fargs_parse(size_t argc,char * argv[],struct opts * opts)80 fargs_parse(size_t argc, char *argv[], struct opts *opts)
81 {
82 struct fargs *f = NULL;
83 char *cp, *ccp;
84 size_t i, j, len = 0;
85
86 /* Allocations. */
87
88 if ((f = calloc(1, sizeof(struct fargs))) == NULL)
89 err(ERR_NOMEM, NULL);
90
91 f->sourcesz = argc - 1;
92 if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL)
93 err(ERR_NOMEM, NULL);
94
95 for (i = 0; i < argc - 1; i++)
96 if ((f->sources[i] = strdup(argv[i])) == NULL)
97 err(ERR_NOMEM, NULL);
98
99 if ((f->sink = strdup(argv[i])) == NULL)
100 err(ERR_NOMEM, NULL);
101
102 /*
103 * Test files for its locality.
104 * If the last is a remote host, then we're sending from the
105 * local to the remote host ("sender" mode).
106 * If the first, remote to local ("receiver" mode).
107 * If neither, a local transfer in sender style.
108 */
109
110 f->mode = FARGS_SENDER;
111
112 if (fargs_is_remote(f->sink)) {
113 f->mode = FARGS_SENDER;
114 if ((f->host = strdup(f->sink)) == NULL)
115 err(ERR_NOMEM, NULL);
116 }
117
118 if (fargs_is_remote(f->sources[0])) {
119 if (f->host != NULL)
120 errx(ERR_SYNTAX, "both source and destination "
121 "cannot be remote files");
122 f->mode = FARGS_RECEIVER;
123 if ((f->host = strdup(f->sources[0])) == NULL)
124 err(ERR_NOMEM, NULL);
125 }
126
127 if (f->host != NULL) {
128 if (strncasecmp(f->host, "rsync://", 8) == 0) {
129 /* rsync://host[:port]/module[/path] */
130 f->remote = 1;
131 len = strlen(f->host) - 8 + 1;
132 memmove(f->host, f->host + 8, len);
133 if ((cp = strchr(f->host, '/')) == NULL)
134 errx(ERR_SYNTAX,
135 "rsync protocol requires a module name");
136 *cp++ = '\0';
137 f->module = cp;
138 if ((cp = strchr(f->module, '/')) != NULL)
139 *cp = '\0';
140 if ((cp = strchr(f->host, ':')) != NULL) {
141 /* host:port --> extract port */
142 *cp++ = '\0';
143 opts->port = cp;
144 }
145 } else {
146 /* host:[/path] */
147 cp = strchr(f->host, ':');
148 assert(cp != NULL);
149 *cp++ = '\0';
150 if (*cp == ':') {
151 /* host::module[/path] */
152 f->remote = 1;
153 f->module = ++cp;
154 cp = strchr(f->module, '/');
155 if (cp != NULL)
156 *cp = '\0';
157 }
158 }
159 if ((len = strlen(f->host)) == 0)
160 errx(ERR_SYNTAX, "empty remote host");
161 if (f->remote && strlen(f->module) == 0)
162 errx(ERR_SYNTAX, "empty remote module");
163 }
164
165 /* Make sure we have the same "hostspec" for all files. */
166
167 if (!f->remote) {
168 if (f->mode == FARGS_SENDER)
169 for (i = 0; i < f->sourcesz; i++) {
170 if (!fargs_is_remote(f->sources[i]))
171 continue;
172 errx(ERR_SYNTAX,
173 "remote file in list of local sources: %s",
174 f->sources[i]);
175 }
176 if (f->mode == FARGS_RECEIVER)
177 for (i = 0; i < f->sourcesz; i++) {
178 if (fargs_is_remote(f->sources[i]) &&
179 !fargs_is_daemon(f->sources[i]))
180 continue;
181 if (fargs_is_daemon(f->sources[i]))
182 errx(ERR_SYNTAX,
183 "remote daemon in list of remote "
184 "sources: %s", f->sources[i]);
185 errx(ERR_SYNTAX, "local file in list of "
186 "remote sources: %s", f->sources[i]);
187 }
188 } else {
189 if (f->mode != FARGS_RECEIVER)
190 errx(ERR_SYNTAX, "sender mode for remote "
191 "daemon receivers not yet supported");
192 for (i = 0; i < f->sourcesz; i++) {
193 if (fargs_is_daemon(f->sources[i]))
194 continue;
195 errx(ERR_SYNTAX, "non-remote daemon file "
196 "in list of remote daemon sources: "
197 "%s", f->sources[i]);
198 }
199 }
200
201 /*
202 * If we're not remote and a sender, strip our hostname.
203 * Then exit if we're a sender or a local connection.
204 */
205
206 if (!f->remote) {
207 if (f->host == NULL)
208 return f;
209 if (f->mode == FARGS_SENDER) {
210 assert(f->host != NULL);
211 assert(len > 0);
212 j = strlen(f->sink);
213 memmove(f->sink, f->sink + len + 1, j - len);
214 return f;
215 } else if (f->mode != FARGS_RECEIVER)
216 return f;
217 }
218
219 /*
220 * Now strip the hostnames from the remote host.
221 * rsync://host/module/path -> module/path
222 * host::module/path -> module/path
223 * host:path -> path
224 * Also make sure that the remote hosts are the same.
225 */
226
227 assert(f->host != NULL);
228 assert(len > 0);
229
230 for (i = 0; i < f->sourcesz; i++) {
231 cp = f->sources[i];
232 j = strlen(cp);
233 if (f->remote &&
234 strncasecmp(cp, "rsync://", 8) == 0) {
235 /* rsync://host[:port]/path */
236 size_t module_offset = len;
237 cp += 8;
238 /* skip :port */
239 if ((ccp = strchr(cp, ':')) != NULL) {
240 *ccp = '\0';
241 module_offset += strcspn(ccp + 1, "/") + 1;
242 }
243 if (strncmp(cp, f->host, len) ||
244 (cp[len] != '/' && cp[len] != '\0'))
245 errx(ERR_SYNTAX, "different remote host: %s",
246 f->sources[i]);
247 memmove(f->sources[i],
248 f->sources[i] + module_offset + 8 + 1,
249 j - module_offset - 8);
250 } else if (f->remote && strncmp(cp, "::", 2) == 0) {
251 /* ::path */
252 memmove(f->sources[i],
253 f->sources[i] + 2, j - 1);
254 } else if (f->remote) {
255 /* host::path */
256 if (strncmp(cp, f->host, len) ||
257 (cp[len] != ':' && cp[len] != '\0'))
258 errx(ERR_SYNTAX, "different remote host: %s",
259 f->sources[i]);
260 memmove(f->sources[i], f->sources[i] + len + 2,
261 j - len - 1);
262 } else if (cp[0] == ':') {
263 /* :path */
264 memmove(f->sources[i], f->sources[i] + 1, j);
265 } else {
266 /* host:path */
267 if (strncmp(cp, f->host, len) ||
268 (cp[len] != ':' && cp[len] != '\0'))
269 errx(ERR_SYNTAX, "different remote host: %s",
270 f->sources[i]);
271 memmove(f->sources[i],
272 f->sources[i] + len + 1, j - len);
273 }
274 }
275
276 return f;
277 }
278
279 static struct opts opts;
280
281 #define OP_ADDRESS 1000
282 #define OP_PORT 1001
283 #define OP_RSYNCPATH 1002
284 #define OP_TIMEOUT 1003
285 #define OP_EXCLUDE 1005
286 #define OP_INCLUDE 1006
287 #define OP_EXCLUDE_FROM 1007
288 #define OP_INCLUDE_FROM 1008
289 #define OP_COMP_DEST 1009
290 #define OP_COPY_DEST 1010
291 #define OP_LINK_DEST 1011
292 #define OP_MAX_SIZE 1012
293 #define OP_MIN_SIZE 1013
294 #define OP_CONTIMEOUT 1014
295
296 const struct option lopts[] = {
297 { "address", required_argument, NULL, OP_ADDRESS },
298 { "archive", no_argument, NULL, 'a' },
299 { "compare-dest", required_argument, NULL, OP_COMP_DEST },
300 #if 0
301 { "copy-dest", required_argument, NULL, OP_COPY_DEST },
302 { "link-dest", required_argument, NULL, OP_LINK_DEST },
303 #endif
304 { "compress", no_argument, NULL, 'z' },
305 { "contimeout", required_argument, NULL, OP_CONTIMEOUT },
306 { "del", no_argument, &opts.del, 1 },
307 { "delete", no_argument, &opts.del, 1 },
308 { "devices", no_argument, &opts.devices, 1 },
309 { "no-devices", no_argument, &opts.devices, 0 },
310 { "dry-run", no_argument, &opts.dry_run, 1 },
311 { "exclude", required_argument, NULL, OP_EXCLUDE },
312 { "exclude-from", required_argument, NULL, OP_EXCLUDE_FROM },
313 { "group", no_argument, &opts.preserve_gids, 1 },
314 { "no-group", no_argument, &opts.preserve_gids, 0 },
315 { "help", no_argument, NULL, 'h' },
316 { "ignore-times", no_argument, NULL, 'I' },
317 { "include", required_argument, NULL, OP_INCLUDE },
318 { "include-from", required_argument, NULL, OP_INCLUDE_FROM },
319 { "links", no_argument, &opts.preserve_links, 1 },
320 { "max-size", required_argument, NULL, OP_MAX_SIZE },
321 { "min-size", required_argument, NULL, OP_MIN_SIZE },
322 { "no-links", no_argument, &opts.preserve_links, 0 },
323 { "no-motd", no_argument, &opts.no_motd, 1 },
324 { "numeric-ids", no_argument, &opts.numeric_ids, 1 },
325 { "omit-dir-times", no_argument, &opts.ignore_dir_times, 1 },
326 { "no-O", no_argument, &opts.ignore_dir_times, 0 },
327 { "no-omit-dir-times", no_argument, &opts.ignore_dir_times, 0 },
328 { "omit-link-times", no_argument, &opts.ignore_link_times, 1 },
329 { "no-J", no_argument, &opts.ignore_link_times, 0 },
330 { "no-omit-link-times", no_argument, &opts.ignore_link_times, 0 },
331 { "owner", no_argument, &opts.preserve_uids, 1 },
332 { "no-owner", no_argument, &opts.preserve_uids, 0 },
333 { "perms", no_argument, &opts.preserve_perms, 1 },
334 { "no-perms", no_argument, &opts.preserve_perms, 0 },
335 { "port", required_argument, NULL, OP_PORT },
336 { "recursive", no_argument, &opts.recursive, 1 },
337 { "no-recursive", no_argument, &opts.recursive, 0 },
338 { "rsh", required_argument, NULL, 'e' },
339 { "rsync-path", required_argument, NULL, OP_RSYNCPATH },
340 { "sender", no_argument, &opts.sender, 1 },
341 { "server", no_argument, &opts.server, 1 },
342 { "size-only", no_argument, &opts.size_only, 1 },
343 { "specials", no_argument, &opts.specials, 1 },
344 { "no-specials", no_argument, &opts.specials, 0 },
345 { "timeout", required_argument, NULL, OP_TIMEOUT },
346 { "times", no_argument, &opts.preserve_times, 1 },
347 { "no-times", no_argument, &opts.preserve_times, 0 },
348 { "verbose", no_argument, &verbose, 1 },
349 { "no-verbose", no_argument, &verbose, 0 },
350 { "version", no_argument, NULL, 'V' },
351 { NULL, 0, NULL, 0 }
352 };
353
354 int
main(int argc,char * argv[])355 main(int argc, char *argv[])
356 {
357 pid_t child;
358 int fds[2], sd = -1, rc, c, st, i, lidx;
359 size_t basedir_cnt = 0;
360 struct sess sess;
361 struct fargs *fargs;
362 char **args;
363 const char *errstr;
364
365 /* Global pledge. */
366
367 if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil",
368 NULL) == -1)
369 err(ERR_IPC, "pledge");
370
371 opts.max_size = opts.min_size = -1;
372
373 while ((c = getopt_long(argc, argv, "aDe:ghIJlnOoprtVvxz",
374 lopts, &lidx)) != -1) {
375 switch (c) {
376 case 'D':
377 opts.devices = 1;
378 opts.specials = 1;
379 break;
380 case 'a':
381 opts.recursive = 1;
382 opts.preserve_links = 1;
383 opts.preserve_perms = 1;
384 opts.preserve_times = 1;
385 opts.preserve_gids = 1;
386 opts.preserve_uids = 1;
387 opts.devices = 1;
388 opts.specials = 1;
389 break;
390 case 'e':
391 opts.ssh_prog = optarg;
392 break;
393 case 'g':
394 opts.preserve_gids = 1;
395 break;
396 case 'I':
397 opts.ignore_times = 1;
398 break;
399 case 'J':
400 opts.ignore_link_times = 1;
401 break;
402 case 'l':
403 opts.preserve_links = 1;
404 break;
405 case 'n':
406 opts.dry_run = 1;
407 break;
408 case 'O':
409 opts.ignore_dir_times = 1;
410 break;
411 case 'o':
412 opts.preserve_uids = 1;
413 break;
414 case 'p':
415 opts.preserve_perms = 1;
416 break;
417 case 'r':
418 opts.recursive = 1;
419 break;
420 case 't':
421 opts.preserve_times = 1;
422 break;
423 case 'v':
424 verbose++;
425 break;
426 case 'V':
427 fprintf(stderr, "openrsync %s (protocol version %u)\n",
428 RSYNC_VERSION, RSYNC_PROTOCOL);
429 exit(0);
430 case 'x':
431 opts.one_file_system++;
432 break;
433 case 'z':
434 fprintf(stderr, "%s: -z not supported yet\n", getprogname());
435 break;
436 case 0:
437 /* Non-NULL flag values (e.g., --sender). */
438 break;
439 case OP_ADDRESS:
440 opts.address = optarg;
441 break;
442 case OP_CONTIMEOUT:
443 poll_contimeout = strtonum(optarg, 0, 60*60, &errstr);
444 if (errstr != NULL)
445 errx(ERR_SYNTAX, "timeout is %s: %s",
446 errstr, optarg);
447 break;
448 case OP_PORT:
449 opts.port = optarg;
450 break;
451 case OP_RSYNCPATH:
452 opts.rsync_path = optarg;
453 break;
454 case OP_TIMEOUT:
455 poll_timeout = strtonum(optarg, 0, 60*60, &errstr);
456 if (errstr != NULL)
457 errx(ERR_SYNTAX, "timeout is %s: %s",
458 errstr, optarg);
459 break;
460 case OP_EXCLUDE:
461 if (parse_rule(optarg, RULE_EXCLUDE) == -1)
462 errx(ERR_SYNTAX, "syntax error in exclude: %s",
463 optarg);
464 break;
465 case OP_INCLUDE:
466 if (parse_rule(optarg, RULE_INCLUDE) == -1)
467 errx(ERR_SYNTAX, "syntax error in include: %s",
468 optarg);
469 break;
470 case OP_EXCLUDE_FROM:
471 parse_file(optarg, RULE_EXCLUDE);
472 break;
473 case OP_INCLUDE_FROM:
474 parse_file(optarg, RULE_INCLUDE);
475 break;
476 case OP_COMP_DEST:
477 if (opts.alt_base_mode !=0 &&
478 opts.alt_base_mode != BASE_MODE_COMPARE) {
479 errx(1, "option --%s conflicts with %s",
480 lopts[lidx].name,
481 alt_base_mode(opts.alt_base_mode));
482 }
483 opts.alt_base_mode = BASE_MODE_COMPARE;
484 #if 0
485 goto basedir;
486 case OP_COPY_DEST:
487 if (opts.alt_base_mode !=0 &&
488 opts.alt_base_mode != BASE_MODE_COPY) {
489 errx(1, "option --%s conflicts with %s",
490 lopts[lidx].name,
491 alt_base_mode(opts.alt_base_mode));
492 }
493 opts.alt_base_mode = BASE_MODE_COPY;
494 goto basedir;
495 case OP_LINK_DEST:
496 if (opts.alt_base_mode !=0 &&
497 opts.alt_base_mode != BASE_MODE_LINK) {
498 errx(1, "option --%s conflicts with %s",
499 lopts[lidx].name,
500 alt_base_mode(opts.alt_base_mode));
501 }
502 opts.alt_base_mode = BASE_MODE_LINK;
503
504 basedir:
505 #endif
506 if (basedir_cnt >= MAX_BASEDIR)
507 errx(1, "too many --%s directories specified",
508 lopts[lidx].name);
509 opts.basedir[basedir_cnt++] = optarg;
510 break;
511 case OP_MAX_SIZE:
512 if (scan_scaled(optarg, &opts.max_size) == -1)
513 err(1, "bad max-size");
514 break;
515 case OP_MIN_SIZE:
516 if (scan_scaled(optarg, &opts.min_size) == -1)
517 err(1, "bad min-size");
518 break;
519 case 'h':
520 default:
521 goto usage;
522 }
523 }
524
525 argc -= optind;
526 argv += optind;
527
528 /* FIXME: reference implementation rsync accepts this. */
529
530 if (argc < 2)
531 goto usage;
532
533 if (opts.port == NULL)
534 opts.port = "rsync";
535
536 /* by default and for --contimeout=0 disable poll_contimeout */
537 if (poll_contimeout == 0)
538 poll_contimeout = -1;
539 else
540 poll_contimeout *= 1000;
541
542 /* by default and for --timeout=0 disable poll_timeout */
543 if (poll_timeout == 0)
544 poll_timeout = -1;
545 else
546 poll_timeout *= 1000;
547
548 /*
549 * This is what happens when we're started with the "hidden"
550 * --server option, which is invoked for the rsync on the remote
551 * host by the parent.
552 */
553
554 if (opts.server)
555 exit(rsync_server(&opts, (size_t)argc, argv));
556
557 /*
558 * Now we know that we're the client on the local machine
559 * invoking rsync(1).
560 * At this point, we need to start the client and server
561 * initiation logic.
562 * The client is what we continue running on this host; the
563 * server is what we'll use to connect to the remote and
564 * invoke rsync with the --server option.
565 */
566
567 fargs = fargs_parse(argc, argv, &opts);
568 assert(fargs != NULL);
569
570 /*
571 * If we're contacting an rsync:// daemon, then we don't need to
572 * fork, because we won't start a server ourselves.
573 * Route directly into the socket code, unless a remote shell
574 * has explicitly been specified.
575 */
576
577 if (fargs->remote && opts.ssh_prog == NULL) {
578 assert(fargs->mode == FARGS_RECEIVER);
579 if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) {
580 rc = rsync_socket(&opts, sd, fargs);
581 close(sd);
582 }
583 exit(rc);
584 }
585
586 /* Drop the dns/inet possibility. */
587
588 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil",
589 NULL) == -1)
590 err(ERR_IPC, "pledge");
591
592 /* Create a bidirectional socket and start our child. */
593
594 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1)
595 err(ERR_IPC, "socketpair");
596
597 switch ((child = fork())) {
598 case -1:
599 err(ERR_IPC, "fork");
600 case 0:
601 close(fds[0]);
602 if (pledge("stdio exec", NULL) == -1)
603 err(ERR_IPC, "pledge");
604
605 memset(&sess, 0, sizeof(struct sess));
606 sess.opts = &opts;
607
608 args = fargs_cmdline(&sess, fargs, NULL);
609
610 for (i = 0; args[i] != NULL; i++)
611 LOG2("exec[%d] = %s", i, args[i]);
612
613 /* Make sure the child's stdin is from the sender. */
614 if (dup2(fds[1], STDIN_FILENO) == -1)
615 err(ERR_IPC, "dup2");
616 if (dup2(fds[1], STDOUT_FILENO) == -1)
617 err(ERR_IPC, "dup2");
618 execvp(args[0], args);
619 _exit(ERR_IPC);
620 /* NOTREACHED */
621 default:
622 close(fds[1]);
623 if (!fargs->remote)
624 rc = rsync_client(&opts, fds[0], fargs);
625 else
626 rc = rsync_socket(&opts, fds[0], fargs);
627 break;
628 }
629
630 close(fds[0]);
631
632 if (waitpid(child, &st, 0) == -1)
633 err(ERR_WAITPID, "waitpid");
634
635 /*
636 * If we don't already have an error (rc == 0), then inherit the
637 * error code of rsync_server() if it has exited.
638 * If it hasn't exited, it overrides our return value.
639 */
640
641 if (rc == 0) {
642 if (WIFEXITED(st))
643 rc = WEXITSTATUS(st);
644 else if (WIFSIGNALED(st))
645 rc = ERR_TERMIMATED;
646 else
647 rc = ERR_WAITPID;
648 }
649
650 exit(rc);
651 usage:
652 fprintf(stderr, "usage: %s"
653 " [-aDgIJlnOoprtVvx] [-e program] [--address=sourceaddr]\n"
654 "\t[--contimeout=seconds] [--compare-dest=dir] [--del] [--exclude]\n"
655 "\t[--exclude-from=file] [--include] [--include-from=file]\n"
656 "\t[--no-motd] [--numeric-ids] [--port=portnumber]\n"
657 "\t[--rsync-path=program] [--size-only] [--timeout=seconds]\n"
658 "\tsource ... directory\n",
659 getprogname());
660 exit(ERR_SYNTAX);
661 }
662