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