1*b5fa5d51Sclaudio /* $OpenBSD: rsync.c,v 1.56 2024/11/21 13:32:27 claudio Exp $ */ 29a7e9e7fSjob /* 39a7e9e7fSjob * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 49a7e9e7fSjob * 59a7e9e7fSjob * Permission to use, copy, modify, and distribute this software for any 69a7e9e7fSjob * purpose with or without fee is hereby granted, provided that the above 79a7e9e7fSjob * copyright notice and this permission notice appear in all copies. 89a7e9e7fSjob * 99a7e9e7fSjob * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 109a7e9e7fSjob * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 119a7e9e7fSjob * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 129a7e9e7fSjob * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 139a7e9e7fSjob * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 149a7e9e7fSjob * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 159a7e9e7fSjob * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 169a7e9e7fSjob */ 179a7e9e7fSjob 1808db1177Sclaudio #include <sys/queue.h> 19cfc09c7bSclaudio #include <sys/stat.h> 20cfc09c7bSclaudio #include <sys/wait.h> 2128d71d25Sderaadt #include <netinet/in.h> 229a7e9e7fSjob #include <err.h> 23cfc09c7bSclaudio #include <errno.h> 24cfc09c7bSclaudio #include <poll.h> 259a7e9e7fSjob #include <resolv.h> 26cfc09c7bSclaudio #include <signal.h> 279a7e9e7fSjob #include <stdio.h> 289a7e9e7fSjob #include <stdlib.h> 299a7e9e7fSjob #include <string.h> 30cfc09c7bSclaudio #include <unistd.h> 3108db1177Sclaudio #include <imsg.h> 329a7e9e7fSjob 339a7e9e7fSjob #include "extern.h" 349a7e9e7fSjob 35542275b6Sjob #define __STRINGIFY(x) #x 36542275b6Sjob #define STRINGIFY(x) __STRINGIFY(x) 37542275b6Sjob 389a7e9e7fSjob /* 39cfc09c7bSclaudio * A running rsync process. 40cfc09c7bSclaudio * We can have multiple of these simultaneously and need to keep track 41cfc09c7bSclaudio * of which process maps to which request. 42cfc09c7bSclaudio */ 433e9f5857Sclaudio struct rsync { 443e9f5857Sclaudio TAILQ_ENTRY(rsync) entry; 45cfc09c7bSclaudio char *uri; /* uri of this rsync proc */ 463e9f5857Sclaudio char *dst; /* destination directory */ 473e9f5857Sclaudio char *compdst; /* compare against directory */ 48b6884e9fSclaudio unsigned int id; /* identity of request */ 49cfc09c7bSclaudio pid_t pid; /* pid of process or 0 if unassociated */ 50cfc09c7bSclaudio }; 51cfc09c7bSclaudio 523e9f5857Sclaudio static TAILQ_HEAD(, rsync) states = TAILQ_HEAD_INITIALIZER(states); 533e9f5857Sclaudio 54cfc09c7bSclaudio /* 55402543e6Sclaudio * Return the base of a rsync URI (rsync://hostname/module). The 56402543e6Sclaudio * caRepository provided by the RIR CAs point deeper than they should 57402543e6Sclaudio * which would result in many rsync calls for almost every subdirectory. 583a50f0a9Sjmc * This is inefficient so instead crop the URI to a common base. 59402543e6Sclaudio * The returned string needs to be freed by the caller. 609a7e9e7fSjob */ 61402543e6Sclaudio char * 62402543e6Sclaudio rsync_base_uri(const char *uri) 639a7e9e7fSjob { 64402543e6Sclaudio const char *host, *module, *rest; 65ec185dadSclaudio char *base_uri; 669a7e9e7fSjob 679a7e9e7fSjob /* Case-insensitive rsync URI. */ 680610060dSjob if (strncasecmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) { 699a7e9e7fSjob warnx("%s: not using rsync schema", uri); 70402543e6Sclaudio return NULL; 719a7e9e7fSjob } 729a7e9e7fSjob 739a7e9e7fSjob /* Parse the non-zero-length hostname. */ 74b4964d69Stb host = uri + RSYNC_PROTO_LEN; 759a7e9e7fSjob 769a7e9e7fSjob if ((module = strchr(host, '/')) == NULL) { 779a7e9e7fSjob warnx("%s: missing rsync module", uri); 78402543e6Sclaudio return NULL; 799a7e9e7fSjob } else if (module == host) { 809a7e9e7fSjob warnx("%s: zero-length rsync host", uri); 81402543e6Sclaudio return NULL; 829a7e9e7fSjob } 839a7e9e7fSjob 849a7e9e7fSjob /* The non-zero-length module follows the hostname. */ 859a7e9e7fSjob module++; 86402543e6Sclaudio if (*module == '\0') { 87402543e6Sclaudio warnx("%s: zero-length rsync module", uri); 88402543e6Sclaudio return NULL; 89402543e6Sclaudio } 909a7e9e7fSjob 919a7e9e7fSjob /* The path component is optional. */ 92402543e6Sclaudio if ((rest = strchr(module, '/')) == NULL) { 93ec185dadSclaudio if ((base_uri = strdup(uri)) == NULL) 94ec185dadSclaudio err(1, NULL); 95ec185dadSclaudio return base_uri; 96402543e6Sclaudio } else if (rest == module) { 979a7e9e7fSjob warnx("%s: zero-length module", uri); 98402543e6Sclaudio return NULL; 999a7e9e7fSjob } 1009a7e9e7fSjob 101ec185dadSclaudio if ((base_uri = strndup(uri, rest - uri)) == NULL) 102ec185dadSclaudio err(1, NULL); 103ec185dadSclaudio return base_uri; 1049a7e9e7fSjob } 105cfc09c7bSclaudio 106b435c97dSclaudio /* 107b435c97dSclaudio * The directory passed as --compare-dest needs to be relative to 108b435c97dSclaudio * the destination directory. This function takes care of that. 109b435c97dSclaudio */ 110b435c97dSclaudio static char * 111b435c97dSclaudio rsync_fixup_dest(char *destdir, char *compdir) 112b435c97dSclaudio { 113b435c97dSclaudio const char *dotdot = "../../../../../../"; /* should be enough */ 114b435c97dSclaudio int dirs = 1; 115b435c97dSclaudio char *fn; 116b435c97dSclaudio char c; 117b435c97dSclaudio 118b435c97dSclaudio while ((c = *destdir++) != '\0') 119b435c97dSclaudio if (c == '/') 120b435c97dSclaudio dirs++; 121b435c97dSclaudio 122b435c97dSclaudio if (dirs > 6) 123b435c97dSclaudio /* too deep for us */ 124b435c97dSclaudio return NULL; 125b435c97dSclaudio 126b435c97dSclaudio if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1) 127b435c97dSclaudio err(1, NULL); 128b435c97dSclaudio return fn; 129b435c97dSclaudio } 130b435c97dSclaudio 1313e9f5857Sclaudio static pid_t 1323e9f5857Sclaudio exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst, 1333e9f5857Sclaudio char *compdst) 1343e9f5857Sclaudio { 1353e9f5857Sclaudio pid_t pid; 1363e9f5857Sclaudio char *args[32]; 1373e9f5857Sclaudio char *reldst; 1383e9f5857Sclaudio int i; 1393e9f5857Sclaudio 1403e9f5857Sclaudio if ((pid = fork()) == -1) 1413e9f5857Sclaudio err(1, "fork"); 1423e9f5857Sclaudio 1433e9f5857Sclaudio if (pid == 0) { 1443e9f5857Sclaudio if (pledge("stdio exec", NULL) == -1) 1453e9f5857Sclaudio err(1, "pledge"); 1463e9f5857Sclaudio i = 0; 1473e9f5857Sclaudio args[i++] = (char *)prog; 148eb427634Sjob args[i++] = "-rtO"; 1493e9f5857Sclaudio args[i++] = "--no-motd"; 150aed5e91bSjob args[i++] = "--min-size=" STRINGIFY(MIN_FILE_SIZE); 1513e9f5857Sclaudio args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE); 1523e9f5857Sclaudio args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT); 1533e9f5857Sclaudio args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT); 1543e9f5857Sclaudio args[i++] = "--include=*/"; 1553e9f5857Sclaudio args[i++] = "--include=*.cer"; 1563e9f5857Sclaudio args[i++] = "--include=*.crl"; 1573e9f5857Sclaudio args[i++] = "--include=*.gbr"; 1583e9f5857Sclaudio args[i++] = "--include=*.mft"; 1593e9f5857Sclaudio args[i++] = "--include=*.roa"; 1603e9f5857Sclaudio args[i++] = "--include=*.asa"; 161ee2a33daSjob args[i++] = "--include=*.tak"; 16253ff252dSjob args[i++] = "--include=*.spl"; 1633e9f5857Sclaudio args[i++] = "--exclude=*"; 1643e9f5857Sclaudio if (bind_addr != NULL) { 1653e9f5857Sclaudio args[i++] = "--address"; 1663e9f5857Sclaudio args[i++] = (char *)bind_addr; 1673e9f5857Sclaudio } 1683e9f5857Sclaudio if (compdst != NULL && 1693e9f5857Sclaudio (reldst = rsync_fixup_dest(dst, compdst)) != NULL) { 1703e9f5857Sclaudio args[i++] = "--compare-dest"; 1713e9f5857Sclaudio args[i++] = reldst; 1723e9f5857Sclaudio } 1733e9f5857Sclaudio args[i++] = uri; 1743e9f5857Sclaudio args[i++] = dst; 1753e9f5857Sclaudio args[i] = NULL; 1763e9f5857Sclaudio /* XXX args overflow not prevented */ 1773e9f5857Sclaudio execvp(args[0], args); 1783e9f5857Sclaudio err(1, "%s: execvp", prog); 1793e9f5857Sclaudio } 1803e9f5857Sclaudio 1813e9f5857Sclaudio return pid; 1823e9f5857Sclaudio } 1833e9f5857Sclaudio 1843e9f5857Sclaudio static void 1853e9f5857Sclaudio rsync_new(unsigned int id, char *uri, char *dst, char *compdst) 1863e9f5857Sclaudio { 1873e9f5857Sclaudio struct rsync *s; 1883e9f5857Sclaudio 1893e9f5857Sclaudio if ((s = calloc(1, sizeof(*s))) == NULL) 1903e9f5857Sclaudio err(1, NULL); 1913e9f5857Sclaudio 1923e9f5857Sclaudio s->id = id; 1933e9f5857Sclaudio s->uri = uri; 1943e9f5857Sclaudio s->dst = dst; 1953e9f5857Sclaudio s->compdst = compdst; 1963e9f5857Sclaudio 1973e9f5857Sclaudio TAILQ_INSERT_TAIL(&states, s, entry); 1983e9f5857Sclaudio } 1993e9f5857Sclaudio 2003e9f5857Sclaudio static void 2013e9f5857Sclaudio rsync_free(struct rsync *s) 2023e9f5857Sclaudio { 2033e9f5857Sclaudio TAILQ_REMOVE(&states, s, entry); 2043e9f5857Sclaudio free(s->uri); 2053e9f5857Sclaudio free(s->dst); 2063e9f5857Sclaudio free(s->compdst); 2073e9f5857Sclaudio free(s); 2083e9f5857Sclaudio } 2093e9f5857Sclaudio 210cfc09c7bSclaudio static void 211cfc09c7bSclaudio proc_child(int signal) 212cfc09c7bSclaudio { 213cfc09c7bSclaudio 214cfc09c7bSclaudio /* Nothing: just discard. */ 215cfc09c7bSclaudio } 216cfc09c7bSclaudio 217cfc09c7bSclaudio /* 218cfc09c7bSclaudio * Process used for synchronising repositories. 219cfc09c7bSclaudio * This simply waits to be told which repository to synchronise, then 220cfc09c7bSclaudio * does so. 221cfc09c7bSclaudio * It then responds with the identifier of the repo that it updated. 222cfc09c7bSclaudio * It only exits cleanly when fd is closed. 223cfc09c7bSclaudio */ 224cfc09c7bSclaudio void 225cfc09c7bSclaudio proc_rsync(char *prog, char *bind_addr, int fd) 226cfc09c7bSclaudio { 2273e9f5857Sclaudio int nprocs = 0, npending = 0, rc = 0; 228cfc09c7bSclaudio struct pollfd pfd; 22925d36c5cSclaudio struct msgbuf *msgq; 230*b5fa5d51Sclaudio struct ibuf *b; 231cfc09c7bSclaudio sigset_t mask, oldmask; 2323e9f5857Sclaudio struct rsync *s, *ns; 233cfc09c7bSclaudio 2341db5fd2bSclaudio if (pledge("stdio rpath proc exec unveil", NULL) == -1) 2351db5fd2bSclaudio err(1, "pledge"); 23608db1177Sclaudio 237*b5fa5d51Sclaudio if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) == 238*b5fa5d51Sclaudio NULL) 23925d36c5cSclaudio err(1, NULL); 240a6a6bc2cSclaudio pfd.fd = fd; 241cfc09c7bSclaudio 242cfc09c7bSclaudio /* 243cfc09c7bSclaudio * Unveil the command we want to run. 244cfc09c7bSclaudio * If this has a pathname component in it, interpret as a file 245cfc09c7bSclaudio * and unveil the file directly. 246cfc09c7bSclaudio * Otherwise, look up the command in our PATH. 247cfc09c7bSclaudio */ 248cfc09c7bSclaudio 249cfc09c7bSclaudio if (strchr(prog, '/') == NULL) { 25095b65f7cSderaadt const char *pp; 25195b65f7cSderaadt char *save, *cmd, *path; 25295b65f7cSderaadt struct stat stt; 25395b65f7cSderaadt 254cfc09c7bSclaudio if (getenv("PATH") == NULL) 255cfc09c7bSclaudio errx(1, "PATH is unset"); 256cfc09c7bSclaudio if ((path = strdup(getenv("PATH"))) == NULL) 2572c3e7bceSclaudio err(1, NULL); 258cfc09c7bSclaudio save = path; 259cfc09c7bSclaudio while ((pp = strsep(&path, ":")) != NULL) { 260cfc09c7bSclaudio if (*pp == '\0') 261cfc09c7bSclaudio continue; 262cfc09c7bSclaudio if (asprintf(&cmd, "%s/%s", pp, prog) == -1) 2632c3e7bceSclaudio err(1, NULL); 264cfc09c7bSclaudio if (lstat(cmd, &stt) == -1) { 265cfc09c7bSclaudio free(cmd); 266cfc09c7bSclaudio continue; 267cfc09c7bSclaudio } else if (unveil(cmd, "x") == -1) 268cfc09c7bSclaudio err(1, "%s: unveil", cmd); 269cfc09c7bSclaudio free(cmd); 270cfc09c7bSclaudio break; 271cfc09c7bSclaudio } 272cfc09c7bSclaudio free(save); 273cfc09c7bSclaudio } else if (unveil(prog, "x") == -1) 274cfc09c7bSclaudio err(1, "%s: unveil", prog); 275cfc09c7bSclaudio 276a0dad605Sclaudio if (pledge("stdio proc exec", NULL) == -1) 2776d83c0a3Sclaudio err(1, "pledge"); 2786d83c0a3Sclaudio 279cfc09c7bSclaudio /* Initialise retriever for children exiting. */ 280cfc09c7bSclaudio 281cfc09c7bSclaudio if (sigemptyset(&mask) == -1) 282cfc09c7bSclaudio err(1, NULL); 283cfc09c7bSclaudio if (signal(SIGCHLD, proc_child) == SIG_ERR) 284cfc09c7bSclaudio err(1, NULL); 285cfc09c7bSclaudio if (sigaddset(&mask, SIGCHLD) == -1) 286cfc09c7bSclaudio err(1, NULL); 287cfc09c7bSclaudio if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) 288cfc09c7bSclaudio err(1, NULL); 289cfc09c7bSclaudio 290cfc09c7bSclaudio for (;;) { 291b435c97dSclaudio char *uri, *dst, *compdst; 292b6884e9fSclaudio unsigned int id; 29395b65f7cSderaadt pid_t pid; 29495b65f7cSderaadt int st; 29595b65f7cSderaadt 29636dac55eSclaudio pfd.events = 0; 29736dac55eSclaudio pfd.events |= POLLIN; 29825d36c5cSclaudio if (msgbuf_queuelen(msgq) > 0) 29908db1177Sclaudio pfd.events |= POLLOUT; 30008db1177Sclaudio 3013e9f5857Sclaudio if (npending > 0 && nprocs < MAX_RSYNC_REQUESTS) { 3023e9f5857Sclaudio TAILQ_FOREACH(s, &states, entry) { 3033e9f5857Sclaudio if (s->pid == 0) { 3043e9f5857Sclaudio s->pid = exec_rsync(prog, bind_addr, 3053e9f5857Sclaudio s->uri, s->dst, s->compdst); 3063e9f5857Sclaudio if (++nprocs >= MAX_RSYNC_REQUESTS) 3073e9f5857Sclaudio break; 3083e9f5857Sclaudio if (--npending == 0) 3093e9f5857Sclaudio break; 3103e9f5857Sclaudio } 3113e9f5857Sclaudio } 3123e9f5857Sclaudio } 3133e9f5857Sclaudio 314cfc09c7bSclaudio if (ppoll(&pfd, 1, NULL, &oldmask) == -1) { 315cfc09c7bSclaudio if (errno != EINTR) 316cfc09c7bSclaudio err(1, "ppoll"); 317cfc09c7bSclaudio 318cfc09c7bSclaudio /* 319cfc09c7bSclaudio * If we've received an EINTR, it means that one 320cfc09c7bSclaudio * of our children has exited and we can reap it 321cfc09c7bSclaudio * and look up its identifier. 322cfc09c7bSclaudio * Then we respond to the parent. 323cfc09c7bSclaudio */ 324cfc09c7bSclaudio 325cfc09c7bSclaudio while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) { 326cfc09c7bSclaudio int ok = 1; 327cfc09c7bSclaudio 3283e9f5857Sclaudio TAILQ_FOREACH(s, &states, entry) 3293e9f5857Sclaudio if (s->pid == pid) 330cfc09c7bSclaudio break; 3313e9f5857Sclaudio if (s == NULL) 332b435c97dSclaudio errx(1, "waitpid: %d unexpected", pid); 333cfc09c7bSclaudio 334cfc09c7bSclaudio if (!WIFEXITED(st)) { 335cfc09c7bSclaudio warnx("rsync %s terminated abnormally", 3363e9f5857Sclaudio s->uri); 337cfc09c7bSclaudio rc = 1; 338cfc09c7bSclaudio ok = 0; 339cfc09c7bSclaudio } else if (WEXITSTATUS(st) != 0) { 3403e9f5857Sclaudio warnx("rsync %s failed", s->uri); 341cfc09c7bSclaudio ok = 0; 342cfc09c7bSclaudio } 343cfc09c7bSclaudio 34425f7afeeSclaudio b = io_new_buffer(); 3453e9f5857Sclaudio io_simple_buffer(b, &s->id, sizeof(s->id)); 34608db1177Sclaudio io_simple_buffer(b, &ok, sizeof(ok)); 34725d36c5cSclaudio io_close_buffer(msgq, b); 34826b5971fSclaudio 3493e9f5857Sclaudio rsync_free(s); 35036dac55eSclaudio nprocs--; 351cfc09c7bSclaudio } 352cfc09c7bSclaudio if (pid == -1 && errno != ECHILD) 353cfc09c7bSclaudio err(1, "waitpid"); 3543e9f5857Sclaudio 355cfc09c7bSclaudio continue; 356cfc09c7bSclaudio } 357cfc09c7bSclaudio 35808db1177Sclaudio if (pfd.revents & POLLOUT) { 35925d36c5cSclaudio if (msgbuf_write(fd, msgq) == -1) { 3609aadc625Sclaudio if (errno == EPIPE) 36108db1177Sclaudio errx(1, "write: connection closed"); 3629aadc625Sclaudio else 36308db1177Sclaudio err(1, "write"); 36408db1177Sclaudio } 36508db1177Sclaudio } 36608db1177Sclaudio 3672defcb52Sclaudio /* connection closed */ 3682defcb52Sclaudio if (pfd.revents & POLLHUP) 3692defcb52Sclaudio break; 3702defcb52Sclaudio 37108db1177Sclaudio if (!(pfd.revents & POLLIN)) 37208db1177Sclaudio continue; 37308db1177Sclaudio 374*b5fa5d51Sclaudio switch (ibuf_read(fd, msgq)) { 375*b5fa5d51Sclaudio case -1: 376*b5fa5d51Sclaudio err(1, "ibuf_read"); 377*b5fa5d51Sclaudio case 0: 378*b5fa5d51Sclaudio errx(1, "ibuf_read: connection closed"); 379*b5fa5d51Sclaudio } 3807eb79a4aSclaudio 381*b5fa5d51Sclaudio while ((b = io_buf_get(msgq)) != NULL) { 382cfc09c7bSclaudio /* Read host and module. */ 3837eb79a4aSclaudio io_read_buf(b, &id, sizeof(id)); 3847eb79a4aSclaudio io_read_str(b, &dst); 385b435c97dSclaudio io_read_str(b, &compdst); 3867eb79a4aSclaudio io_read_str(b, &uri); 3877eb79a4aSclaudio 3887eb79a4aSclaudio ibuf_free(b); 3897eb79a4aSclaudio 3903e9f5857Sclaudio if (dst != NULL) { 3913e9f5857Sclaudio rsync_new(id, uri, dst, compdst); 3923e9f5857Sclaudio npending++; 3933e9f5857Sclaudio } else { 3943e9f5857Sclaudio TAILQ_FOREACH(s, &states, entry) 3953e9f5857Sclaudio if (s->id == id) 396cfc09c7bSclaudio break; 3973e9f5857Sclaudio if (s != NULL) { 3983e9f5857Sclaudio if (s->pid != 0) 3993e9f5857Sclaudio kill(s->pid, SIGTERM); 4003e9f5857Sclaudio else 4013e9f5857Sclaudio rsync_free(s); 4023e9f5857Sclaudio } 4033e9f5857Sclaudio } 404cfc09c7bSclaudio } 405*b5fa5d51Sclaudio } 406cfc09c7bSclaudio 407cfc09c7bSclaudio /* No need for these to be hanging around. */ 4083e9f5857Sclaudio TAILQ_FOREACH_SAFE(s, &states, entry, ns) { 4093e9f5857Sclaudio if (s->pid != 0) 4103e9f5857Sclaudio kill(s->pid, SIGTERM); 4113e9f5857Sclaudio rsync_free(s); 412cfc09c7bSclaudio } 413cfc09c7bSclaudio 41425d36c5cSclaudio msgbuf_free(msgq); 415cfc09c7bSclaudio exit(rc); 416cfc09c7bSclaudio } 417