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