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