1 /* $Id: main.c,v 1.51 2020/12/15 08:20:42 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(1, "calloc"); 87 88 f->sourcesz = argc - 1; 89 if ((f->sources = calloc(f->sourcesz, sizeof(char *))) == NULL) 90 err(1, "calloc"); 91 92 for (i = 0; i < argc - 1; i++) 93 if ((f->sources[i] = strdup(argv[i])) == NULL) 94 err(1, "strdup"); 95 96 if ((f->sink = strdup(argv[i])) == NULL) 97 err(1, "strdup"); 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(1, "strdup"); 113 } 114 115 if (fargs_is_remote(f->sources[0])) { 116 if (f->host != NULL) 117 errx(1, "both source and destination cannot be remote files"); 118 f->mode = FARGS_RECEIVER; 119 if ((f->host = strdup(f->sources[0])) == NULL) 120 err(1, "strdup"); 121 } 122 123 if (f->host != NULL) { 124 if (strncasecmp(f->host, "rsync://", 8) == 0) { 125 /* rsync://host[:port]/module[/path] */ 126 f->remote = 1; 127 len = strlen(f->host) - 8 + 1; 128 memmove(f->host, f->host + 8, len); 129 if ((cp = strchr(f->host, '/')) == NULL) 130 errx(1, "rsync protocol requires a module name"); 131 *cp++ = '\0'; 132 f->module = cp; 133 if ((cp = strchr(f->module, '/')) != NULL) 134 *cp = '\0'; 135 if ((cp = strchr(f->host, ':'))) { 136 /* host:port --> extract port */ 137 *cp++ = '\0'; 138 opts->port = cp; 139 } 140 } else { 141 /* host:[/path] */ 142 cp = strchr(f->host, ':'); 143 assert(cp != NULL); 144 *cp++ = '\0'; 145 if (*cp == ':') { 146 /* host::module[/path] */ 147 f->remote = 1; 148 f->module = ++cp; 149 cp = strchr(f->module, '/'); 150 if (cp != NULL) 151 *cp = '\0'; 152 } 153 } 154 if ((len = strlen(f->host)) == 0) 155 errx(1, "empty remote host"); 156 if (f->remote && strlen(f->module) == 0) 157 errx(1, "empty remote module"); 158 } 159 160 /* Make sure we have the same "hostspec" for all files. */ 161 162 if (!f->remote) { 163 if (f->mode == FARGS_SENDER) 164 for (i = 0; i < f->sourcesz; i++) { 165 if (!fargs_is_remote(f->sources[i])) 166 continue; 167 errx(1, 168 "remote file in list of local sources: %s", 169 f->sources[i]); 170 } 171 if (f->mode == FARGS_RECEIVER) 172 for (i = 0; i < f->sourcesz; i++) { 173 if (fargs_is_remote(f->sources[i]) && 174 !fargs_is_daemon(f->sources[i])) 175 continue; 176 if (fargs_is_daemon(f->sources[i])) 177 errx(1, "remote daemon in list of " 178 "remote sources: %s", 179 f->sources[i]); 180 errx(1, "local file in list of remote sources: %s", 181 f->sources[i]); 182 } 183 } else { 184 if (f->mode != FARGS_RECEIVER) 185 errx(1, "sender mode for remote " 186 "daemon receivers not yet supported"); 187 for (i = 0; i < f->sourcesz; i++) { 188 if (fargs_is_daemon(f->sources[i])) 189 continue; 190 errx(1, "non-remote daemon file " 191 "in list of remote daemon sources: " 192 "%s", f->sources[i]); 193 } 194 } 195 196 /* 197 * If we're not remote and a sender, strip our hostname. 198 * Then exit if we're a sender or a local connection. 199 */ 200 201 if (!f->remote) { 202 if (f->host == NULL) 203 return f; 204 if (f->mode == FARGS_SENDER) { 205 assert(f->host != NULL); 206 assert(len > 0); 207 j = strlen(f->sink); 208 memmove(f->sink, f->sink + len + 1, j - len); 209 return f; 210 } else if (f->mode != FARGS_RECEIVER) 211 return f; 212 } 213 214 /* 215 * Now strip the hostnames from the remote host. 216 * rsync://host/module/path -> module/path 217 * host::module/path -> module/path 218 * host:path -> path 219 * Also make sure that the remote hosts are the same. 220 */ 221 222 assert(f->host != NULL); 223 assert(len > 0); 224 225 for (i = 0; i < f->sourcesz; i++) { 226 cp = f->sources[i]; 227 j = strlen(cp); 228 if (f->remote && 229 strncasecmp(cp, "rsync://", 8) == 0) { 230 /* rsync://path */ 231 cp += 8; 232 if ((ccp = strchr(cp, ':'))) /* skip :port */ 233 *ccp = '\0'; 234 if (strncmp(cp, f->host, len) || 235 (cp[len] != '/' && cp[len] != '\0')) 236 errx(1, "different remote host: %s", 237 f->sources[i]); 238 memmove(f->sources[i], 239 f->sources[i] + len + 8 + 1, 240 j - len - 8); 241 } else if (f->remote && strncmp(cp, "::", 2) == 0) { 242 /* ::path */ 243 memmove(f->sources[i], 244 f->sources[i] + 2, j - 1); 245 } else if (f->remote) { 246 /* host::path */ 247 if (strncmp(cp, f->host, len) || 248 (cp[len] != ':' && cp[len] != '\0')) 249 errx(1, "different remote host: %s", 250 f->sources[i]); 251 memmove(f->sources[i], f->sources[i] + len + 2, 252 j - len - 1); 253 } else if (cp[0] == ':') { 254 /* :path */ 255 memmove(f->sources[i], f->sources[i] + 1, j); 256 } else { 257 /* host:path */ 258 if (strncmp(cp, f->host, len) || 259 (cp[len] != ':' && cp[len] != '\0')) 260 errx(1, "different remote host: %s", 261 f->sources[i]); 262 memmove(f->sources[i], 263 f->sources[i] + len + 1, j - len); 264 } 265 } 266 267 return f; 268 } 269 270 int 271 main(int argc, char *argv[]) 272 { 273 struct opts opts; 274 pid_t child; 275 int fds[2], sd = -1, rc, c, st, i; 276 struct sess sess; 277 struct fargs *fargs; 278 char **args; 279 const char *errstr; 280 const struct option lopts[] = { 281 { "port", required_argument, NULL, 3 }, 282 { "rsh", required_argument, NULL, 'e' }, 283 { "rsync-path", required_argument, NULL, 1 }, 284 { "sender", no_argument, &opts.sender, 1 }, 285 { "server", no_argument, &opts.server, 1 }, 286 { "dry-run", no_argument, &opts.dry_run, 1 }, 287 { "version", no_argument, NULL, 2 }, 288 { "archive", no_argument, NULL, 'a' }, 289 { "help", no_argument, NULL, 'h' }, 290 { "compress", no_argument, NULL, 'z' }, 291 { "del", no_argument, &opts.del, 1 }, 292 { "delete", no_argument, &opts.del, 1 }, 293 { "devices", no_argument, &opts.devices, 1 }, 294 { "no-devices", no_argument, &opts.devices, 0 }, 295 { "group", no_argument, &opts.preserve_gids, 1 }, 296 { "no-group", no_argument, &opts.preserve_gids, 0 }, 297 { "links", no_argument, &opts.preserve_links, 1 }, 298 { "no-links", no_argument, &opts.preserve_links, 0 }, 299 { "owner", no_argument, &opts.preserve_uids, 1 }, 300 { "no-owner", no_argument, &opts.preserve_uids, 0 }, 301 { "perms", no_argument, &opts.preserve_perms, 1 }, 302 { "no-perms", no_argument, &opts.preserve_perms, 0 }, 303 { "numeric-ids", no_argument, &opts.numeric_ids, 1 }, 304 { "recursive", no_argument, &opts.recursive, 1 }, 305 { "no-recursive", no_argument, &opts.recursive, 0 }, 306 { "specials", no_argument, &opts.specials, 1 }, 307 { "no-specials", no_argument, &opts.specials, 0 }, 308 { "timeout", required_argument, NULL, 5 }, 309 { "times", no_argument, &opts.preserve_times, 1 }, 310 { "no-times", no_argument, &opts.preserve_times, 0 }, 311 { "verbose", no_argument, &verbose, 1 }, 312 { "no-verbose", no_argument, &verbose, 0 }, 313 { "address", required_argument, NULL, 4 }, 314 { NULL, 0, NULL, 0 }}; 315 316 /* Global pledge. */ 317 318 if (pledge("stdio unix rpath wpath cpath dpath inet fattr chown dns getpw proc exec unveil", 319 NULL) == -1) 320 err(1, "pledge"); 321 322 memset(&opts, 0, sizeof(struct opts)); 323 324 while ((c = getopt_long(argc, argv, "Dae:ghlnoprtvxz", lopts, NULL)) 325 != -1) { 326 switch (c) { 327 case 'D': 328 opts.devices = 1; 329 opts.specials = 1; 330 break; 331 case 'a': 332 opts.recursive = 1; 333 opts.preserve_links = 1; 334 opts.preserve_perms = 1; 335 opts.preserve_times = 1; 336 opts.preserve_gids = 1; 337 opts.preserve_uids = 1; 338 opts.devices = 1; 339 opts.specials = 1; 340 break; 341 case 'e': 342 opts.ssh_prog = optarg; 343 break; 344 case 'g': 345 opts.preserve_gids = 1; 346 break; 347 case 'l': 348 opts.preserve_links = 1; 349 break; 350 case 'n': 351 opts.dry_run = 1; 352 break; 353 case 'o': 354 opts.preserve_uids = 1; 355 break; 356 case 'p': 357 opts.preserve_perms = 1; 358 break; 359 case 'r': 360 opts.recursive = 1; 361 break; 362 case 't': 363 opts.preserve_times = 1; 364 break; 365 case 'v': 366 verbose++; 367 break; 368 case 'x': 369 opts.one_file_system++; 370 break; 371 case 'z': 372 fprintf(stderr, "%s: -z not supported yet\n", getprogname()); 373 break; 374 case 0: 375 /* Non-NULL flag values (e.g., --sender). */ 376 break; 377 case 1: 378 opts.rsync_path = optarg; 379 break; 380 case 2: 381 fprintf(stderr, "openrsync: protocol version %u\n", 382 RSYNC_PROTOCOL); 383 exit(0); 384 case 3: 385 opts.port = optarg; 386 break; 387 case 4: 388 opts.address = optarg; 389 break; 390 case 5: 391 poll_timeout = strtonum(optarg, 0, 60*60, &errstr); 392 if (errstr != NULL) 393 errx(1, "timeout is %s: %s", errstr, optarg); 394 break; 395 case 'h': 396 default: 397 goto usage; 398 } 399 } 400 401 argc -= optind; 402 argv += optind; 403 404 /* FIXME: reference implementation rsync accepts this. */ 405 406 if (argc < 2) 407 goto usage; 408 409 if (opts.port == NULL) 410 opts.port = "rsync"; 411 412 /* by default and for --timeout=0 disable poll_timeout */ 413 if (poll_timeout == 0) 414 poll_timeout = -1; 415 else 416 poll_timeout *= 1000; 417 418 /* 419 * This is what happens when we're started with the "hidden" 420 * --server option, which is invoked for the rsync on the remote 421 * host by the parent. 422 */ 423 424 if (opts.server) 425 exit(rsync_server(&opts, (size_t)argc, argv)); 426 427 /* 428 * Now we know that we're the client on the local machine 429 * invoking rsync(1). 430 * At this point, we need to start the client and server 431 * initiation logic. 432 * The client is what we continue running on this host; the 433 * server is what we'll use to connect to the remote and 434 * invoke rsync with the --server option. 435 */ 436 437 fargs = fargs_parse(argc, argv, &opts); 438 assert(fargs != NULL); 439 440 /* 441 * If we're contacting an rsync:// daemon, then we don't need to 442 * fork, because we won't start a server ourselves. 443 * Route directly into the socket code, unless a remote shell 444 * has explicitly been specified. 445 */ 446 447 if (fargs->remote && opts.ssh_prog == NULL) { 448 assert(fargs->mode == FARGS_RECEIVER); 449 if ((rc = rsync_connect(&opts, &sd, fargs)) == 0) { 450 rc = rsync_socket(&opts, sd, fargs); 451 close(sd); 452 } 453 exit(rc); 454 } 455 456 /* Drop the dns/inet possibility. */ 457 458 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw proc exec unveil", 459 NULL) == -1) 460 err(1, "pledge"); 461 462 /* Create a bidirectional socket and start our child. */ 463 464 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1) 465 err(1, "socketpair"); 466 467 switch ((child = fork())) { 468 case -1: 469 err(1, "fork"); 470 case 0: 471 close(fds[0]); 472 if (pledge("stdio exec", NULL) == -1) 473 err(1, "pledge"); 474 475 memset(&sess, 0, sizeof(struct sess)); 476 sess.opts = &opts; 477 478 if ((args = fargs_cmdline(&sess, fargs, NULL)) == NULL) { 479 ERRX1("fargs_cmdline"); 480 _exit(1); 481 } 482 483 for (i = 0; args[i] != NULL; i++) 484 LOG2("exec[%d] = %s", i, args[i]); 485 486 /* Make sure the child's stdin is from the sender. */ 487 if (dup2(fds[1], STDIN_FILENO) == -1) { 488 ERR("dup2"); 489 _exit(1); 490 } 491 if (dup2(fds[1], STDOUT_FILENO) == -1) { 492 ERR("dup2"); 493 _exit(1); 494 } 495 execvp(args[0], args); 496 _exit(1); 497 /* NOTREACHED */ 498 default: 499 close(fds[1]); 500 if (!fargs->remote) 501 rc = rsync_client(&opts, fds[0], fargs); 502 else 503 rc = rsync_socket(&opts, fds[0], fargs); 504 break; 505 } 506 507 close(fds[0]); 508 509 if (waitpid(child, &st, 0) == -1) 510 err(1, "waitpid"); 511 512 /* 513 * If we don't already have an error (rc == 0), then inherit the 514 * error code of rsync_server() if it has exited. 515 * If it hasn't exited, it overrides our return value. 516 */ 517 518 if (WIFEXITED(st) && rc == 0) 519 rc = WEXITSTATUS(st); 520 else if (!WIFEXITED(st)) 521 rc = 1; 522 523 exit(rc); 524 usage: 525 fprintf(stderr, "usage: %s" 526 " [-aDglnoprtvx] [-e program] [--address=sourceaddr] [--del]\n" 527 "\t[--numeric-ids] [--port=portnumber] [--rsync-path=program]\n" 528 "\t[--timeout=seconds] [--version] source ... directory\n", 529 getprogname()); 530 exit(1); 531 } 532