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