1 /* $OpenBSD: rsync.c,v 1.56 2024/11/21 13:32:27 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 18 #include <sys/queue.h> 19 #include <sys/stat.h> 20 #include <sys/wait.h> 21 #include <netinet/in.h> 22 #include <err.h> 23 #include <errno.h> 24 #include <poll.h> 25 #include <resolv.h> 26 #include <signal.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 #include <imsg.h> 32 33 #include "extern.h" 34 35 #define __STRINGIFY(x) #x 36 #define STRINGIFY(x) __STRINGIFY(x) 37 38 /* 39 * A running rsync process. 40 * We can have multiple of these simultaneously and need to keep track 41 * of which process maps to which request. 42 */ 43 struct rsync { 44 TAILQ_ENTRY(rsync) entry; 45 char *uri; /* uri of this rsync proc */ 46 char *dst; /* destination directory */ 47 char *compdst; /* compare against directory */ 48 unsigned int id; /* identity of request */ 49 pid_t pid; /* pid of process or 0 if unassociated */ 50 }; 51 52 static TAILQ_HEAD(, rsync) states = TAILQ_HEAD_INITIALIZER(states); 53 54 /* 55 * Return the base of a rsync URI (rsync://hostname/module). The 56 * caRepository provided by the RIR CAs point deeper than they should 57 * which would result in many rsync calls for almost every subdirectory. 58 * This is inefficient so instead crop the URI to a common base. 59 * The returned string needs to be freed by the caller. 60 */ 61 char * 62 rsync_base_uri(const char *uri) 63 { 64 const char *host, *module, *rest; 65 char *base_uri; 66 67 /* Case-insensitive rsync URI. */ 68 if (strncasecmp(uri, RSYNC_PROTO, RSYNC_PROTO_LEN) != 0) { 69 warnx("%s: not using rsync schema", uri); 70 return NULL; 71 } 72 73 /* Parse the non-zero-length hostname. */ 74 host = uri + RSYNC_PROTO_LEN; 75 76 if ((module = strchr(host, '/')) == NULL) { 77 warnx("%s: missing rsync module", uri); 78 return NULL; 79 } else if (module == host) { 80 warnx("%s: zero-length rsync host", uri); 81 return NULL; 82 } 83 84 /* The non-zero-length module follows the hostname. */ 85 module++; 86 if (*module == '\0') { 87 warnx("%s: zero-length rsync module", uri); 88 return NULL; 89 } 90 91 /* The path component is optional. */ 92 if ((rest = strchr(module, '/')) == NULL) { 93 if ((base_uri = strdup(uri)) == NULL) 94 err(1, NULL); 95 return base_uri; 96 } else if (rest == module) { 97 warnx("%s: zero-length module", uri); 98 return NULL; 99 } 100 101 if ((base_uri = strndup(uri, rest - uri)) == NULL) 102 err(1, NULL); 103 return base_uri; 104 } 105 106 /* 107 * The directory passed as --compare-dest needs to be relative to 108 * the destination directory. This function takes care of that. 109 */ 110 static char * 111 rsync_fixup_dest(char *destdir, char *compdir) 112 { 113 const char *dotdot = "../../../../../../"; /* should be enough */ 114 int dirs = 1; 115 char *fn; 116 char c; 117 118 while ((c = *destdir++) != '\0') 119 if (c == '/') 120 dirs++; 121 122 if (dirs > 6) 123 /* too deep for us */ 124 return NULL; 125 126 if ((asprintf(&fn, "%.*s%s", dirs * 3, dotdot, compdir)) == -1) 127 err(1, NULL); 128 return fn; 129 } 130 131 static pid_t 132 exec_rsync(const char *prog, const char *bind_addr, char *uri, char *dst, 133 char *compdst) 134 { 135 pid_t pid; 136 char *args[32]; 137 char *reldst; 138 int i; 139 140 if ((pid = fork()) == -1) 141 err(1, "fork"); 142 143 if (pid == 0) { 144 if (pledge("stdio exec", NULL) == -1) 145 err(1, "pledge"); 146 i = 0; 147 args[i++] = (char *)prog; 148 args[i++] = "-rtO"; 149 args[i++] = "--no-motd"; 150 args[i++] = "--min-size=" STRINGIFY(MIN_FILE_SIZE); 151 args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE); 152 args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT); 153 args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT); 154 args[i++] = "--include=*/"; 155 args[i++] = "--include=*.cer"; 156 args[i++] = "--include=*.crl"; 157 args[i++] = "--include=*.gbr"; 158 args[i++] = "--include=*.mft"; 159 args[i++] = "--include=*.roa"; 160 args[i++] = "--include=*.asa"; 161 args[i++] = "--include=*.tak"; 162 args[i++] = "--include=*.spl"; 163 args[i++] = "--exclude=*"; 164 if (bind_addr != NULL) { 165 args[i++] = "--address"; 166 args[i++] = (char *)bind_addr; 167 } 168 if (compdst != NULL && 169 (reldst = rsync_fixup_dest(dst, compdst)) != NULL) { 170 args[i++] = "--compare-dest"; 171 args[i++] = reldst; 172 } 173 args[i++] = uri; 174 args[i++] = dst; 175 args[i] = NULL; 176 /* XXX args overflow not prevented */ 177 execvp(args[0], args); 178 err(1, "%s: execvp", prog); 179 } 180 181 return pid; 182 } 183 184 static void 185 rsync_new(unsigned int id, char *uri, char *dst, char *compdst) 186 { 187 struct rsync *s; 188 189 if ((s = calloc(1, sizeof(*s))) == NULL) 190 err(1, NULL); 191 192 s->id = id; 193 s->uri = uri; 194 s->dst = dst; 195 s->compdst = compdst; 196 197 TAILQ_INSERT_TAIL(&states, s, entry); 198 } 199 200 static void 201 rsync_free(struct rsync *s) 202 { 203 TAILQ_REMOVE(&states, s, entry); 204 free(s->uri); 205 free(s->dst); 206 free(s->compdst); 207 free(s); 208 } 209 210 static void 211 proc_child(int signal) 212 { 213 214 /* Nothing: just discard. */ 215 } 216 217 /* 218 * Process used for synchronising repositories. 219 * This simply waits to be told which repository to synchronise, then 220 * does so. 221 * It then responds with the identifier of the repo that it updated. 222 * It only exits cleanly when fd is closed. 223 */ 224 void 225 proc_rsync(char *prog, char *bind_addr, int fd) 226 { 227 int nprocs = 0, npending = 0, rc = 0; 228 struct pollfd pfd; 229 struct msgbuf *msgq; 230 struct ibuf *b; 231 sigset_t mask, oldmask; 232 struct rsync *s, *ns; 233 234 if (pledge("stdio rpath proc exec unveil", NULL) == -1) 235 err(1, "pledge"); 236 237 if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) == 238 NULL) 239 err(1, NULL); 240 pfd.fd = fd; 241 242 /* 243 * Unveil the command we want to run. 244 * If this has a pathname component in it, interpret as a file 245 * and unveil the file directly. 246 * Otherwise, look up the command in our PATH. 247 */ 248 249 if (strchr(prog, '/') == NULL) { 250 const char *pp; 251 char *save, *cmd, *path; 252 struct stat stt; 253 254 if (getenv("PATH") == NULL) 255 errx(1, "PATH is unset"); 256 if ((path = strdup(getenv("PATH"))) == NULL) 257 err(1, NULL); 258 save = path; 259 while ((pp = strsep(&path, ":")) != NULL) { 260 if (*pp == '\0') 261 continue; 262 if (asprintf(&cmd, "%s/%s", pp, prog) == -1) 263 err(1, NULL); 264 if (lstat(cmd, &stt) == -1) { 265 free(cmd); 266 continue; 267 } else if (unveil(cmd, "x") == -1) 268 err(1, "%s: unveil", cmd); 269 free(cmd); 270 break; 271 } 272 free(save); 273 } else if (unveil(prog, "x") == -1) 274 err(1, "%s: unveil", prog); 275 276 if (pledge("stdio proc exec", NULL) == -1) 277 err(1, "pledge"); 278 279 /* Initialise retriever for children exiting. */ 280 281 if (sigemptyset(&mask) == -1) 282 err(1, NULL); 283 if (signal(SIGCHLD, proc_child) == SIG_ERR) 284 err(1, NULL); 285 if (sigaddset(&mask, SIGCHLD) == -1) 286 err(1, NULL); 287 if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) 288 err(1, NULL); 289 290 for (;;) { 291 char *uri, *dst, *compdst; 292 unsigned int id; 293 pid_t pid; 294 int st; 295 296 pfd.events = 0; 297 pfd.events |= POLLIN; 298 if (msgbuf_queuelen(msgq) > 0) 299 pfd.events |= POLLOUT; 300 301 if (npending > 0 && nprocs < MAX_RSYNC_REQUESTS) { 302 TAILQ_FOREACH(s, &states, entry) { 303 if (s->pid == 0) { 304 s->pid = exec_rsync(prog, bind_addr, 305 s->uri, s->dst, s->compdst); 306 if (++nprocs >= MAX_RSYNC_REQUESTS) 307 break; 308 if (--npending == 0) 309 break; 310 } 311 } 312 } 313 314 if (ppoll(&pfd, 1, NULL, &oldmask) == -1) { 315 if (errno != EINTR) 316 err(1, "ppoll"); 317 318 /* 319 * If we've received an EINTR, it means that one 320 * of our children has exited and we can reap it 321 * and look up its identifier. 322 * Then we respond to the parent. 323 */ 324 325 while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) { 326 int ok = 1; 327 328 TAILQ_FOREACH(s, &states, entry) 329 if (s->pid == pid) 330 break; 331 if (s == NULL) 332 errx(1, "waitpid: %d unexpected", pid); 333 334 if (!WIFEXITED(st)) { 335 warnx("rsync %s terminated abnormally", 336 s->uri); 337 rc = 1; 338 ok = 0; 339 } else if (WEXITSTATUS(st) != 0) { 340 warnx("rsync %s failed", s->uri); 341 ok = 0; 342 } 343 344 b = io_new_buffer(); 345 io_simple_buffer(b, &s->id, sizeof(s->id)); 346 io_simple_buffer(b, &ok, sizeof(ok)); 347 io_close_buffer(msgq, b); 348 349 rsync_free(s); 350 nprocs--; 351 } 352 if (pid == -1 && errno != ECHILD) 353 err(1, "waitpid"); 354 355 continue; 356 } 357 358 if (pfd.revents & POLLOUT) { 359 if (msgbuf_write(fd, msgq) == -1) { 360 if (errno == EPIPE) 361 errx(1, "write: connection closed"); 362 else 363 err(1, "write"); 364 } 365 } 366 367 /* connection closed */ 368 if (pfd.revents & POLLHUP) 369 break; 370 371 if (!(pfd.revents & POLLIN)) 372 continue; 373 374 switch (ibuf_read(fd, msgq)) { 375 case -1: 376 err(1, "ibuf_read"); 377 case 0: 378 errx(1, "ibuf_read: connection closed"); 379 } 380 381 while ((b = io_buf_get(msgq)) != NULL) { 382 /* Read host and module. */ 383 io_read_buf(b, &id, sizeof(id)); 384 io_read_str(b, &dst); 385 io_read_str(b, &compdst); 386 io_read_str(b, &uri); 387 388 ibuf_free(b); 389 390 if (dst != NULL) { 391 rsync_new(id, uri, dst, compdst); 392 npending++; 393 } else { 394 TAILQ_FOREACH(s, &states, entry) 395 if (s->id == id) 396 break; 397 if (s != NULL) { 398 if (s->pid != 0) 399 kill(s->pid, SIGTERM); 400 else 401 rsync_free(s); 402 } 403 } 404 } 405 } 406 407 /* No need for these to be hanging around. */ 408 TAILQ_FOREACH_SAFE(s, &states, entry, ns) { 409 if (s->pid != 0) 410 kill(s->pid, SIGTERM); 411 rsync_free(s); 412 } 413 414 msgbuf_free(msgq); 415 exit(rc); 416 } 417