1 /* $OpenBSD: rsync.c,v 1.46 2022/12/28 21:30:18 jmc 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://", 8) != 0) { 69 warnx("%s: not using rsync schema", uri); 70 return NULL; 71 } 72 73 /* Parse the non-zero-length hostname. */ 74 host = uri + 8; 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++] = "-rt"; 149 args[i++] = "--no-motd"; 150 args[i++] = "--max-size=" STRINGIFY(MAX_FILE_SIZE); 151 args[i++] = "--contimeout=" STRINGIFY(MAX_CONN_TIMEOUT); 152 args[i++] = "--timeout=" STRINGIFY(MAX_IO_TIMEOUT); 153 args[i++] = "--include=*/"; 154 args[i++] = "--include=*.cer"; 155 args[i++] = "--include=*.crl"; 156 args[i++] = "--include=*.gbr"; 157 args[i++] = "--include=*.mft"; 158 args[i++] = "--include=*.roa"; 159 args[i++] = "--include=*.asa"; 160 args[i++] = "--include=*.tak"; 161 args[i++] = "--exclude=*"; 162 if (bind_addr != NULL) { 163 args[i++] = "--address"; 164 args[i++] = (char *)bind_addr; 165 } 166 if (compdst != NULL && 167 (reldst = rsync_fixup_dest(dst, compdst)) != NULL) { 168 args[i++] = "--compare-dest"; 169 args[i++] = reldst; 170 } 171 args[i++] = uri; 172 args[i++] = dst; 173 args[i] = NULL; 174 /* XXX args overflow not prevented */ 175 execvp(args[0], args); 176 err(1, "%s: execvp", prog); 177 } 178 179 return pid; 180 } 181 182 static void 183 rsync_new(unsigned int id, char *uri, char *dst, char *compdst) 184 { 185 struct rsync *s; 186 187 if ((s = calloc(1, sizeof(*s))) == NULL) 188 err(1, NULL); 189 190 s->id = id; 191 s->uri = uri; 192 s->dst = dst; 193 s->compdst = compdst; 194 195 TAILQ_INSERT_TAIL(&states, s, entry); 196 } 197 198 static void 199 rsync_free(struct rsync *s) 200 { 201 TAILQ_REMOVE(&states, s, entry); 202 free(s->uri); 203 free(s->dst); 204 free(s->compdst); 205 free(s); 206 } 207 208 static void 209 proc_child(int signal) 210 { 211 212 /* Nothing: just discard. */ 213 } 214 215 /* 216 * Process used for synchronising repositories. 217 * This simply waits to be told which repository to synchronise, then 218 * does so. 219 * It then responds with the identifier of the repo that it updated. 220 * It only exits cleanly when fd is closed. 221 */ 222 void 223 proc_rsync(char *prog, char *bind_addr, int fd) 224 { 225 int nprocs = 0, npending = 0, rc = 0; 226 struct pollfd pfd; 227 struct msgbuf msgq; 228 struct ibuf *b, *inbuf = NULL; 229 sigset_t mask, oldmask; 230 struct rsync *s, *ns; 231 232 if (pledge("stdio rpath proc exec unveil", NULL) == -1) 233 err(1, "pledge"); 234 235 pfd.fd = fd; 236 msgbuf_init(&msgq); 237 msgq.fd = fd; 238 239 /* 240 * Unveil the command we want to run. 241 * If this has a pathname component in it, interpret as a file 242 * and unveil the file directly. 243 * Otherwise, look up the command in our PATH. 244 */ 245 246 if (strchr(prog, '/') == NULL) { 247 const char *pp; 248 char *save, *cmd, *path; 249 struct stat stt; 250 251 if (getenv("PATH") == NULL) 252 errx(1, "PATH is unset"); 253 if ((path = strdup(getenv("PATH"))) == NULL) 254 err(1, NULL); 255 save = path; 256 while ((pp = strsep(&path, ":")) != NULL) { 257 if (*pp == '\0') 258 continue; 259 if (asprintf(&cmd, "%s/%s", pp, prog) == -1) 260 err(1, NULL); 261 if (lstat(cmd, &stt) == -1) { 262 free(cmd); 263 continue; 264 } else if (unveil(cmd, "x") == -1) 265 err(1, "%s: unveil", cmd); 266 free(cmd); 267 break; 268 } 269 free(save); 270 } else if (unveil(prog, "x") == -1) 271 err(1, "%s: unveil", prog); 272 273 if (pledge("stdio proc exec", NULL) == -1) 274 err(1, "pledge"); 275 276 /* Initialise retriever for children exiting. */ 277 278 if (sigemptyset(&mask) == -1) 279 err(1, NULL); 280 if (signal(SIGCHLD, proc_child) == SIG_ERR) 281 err(1, NULL); 282 if (sigaddset(&mask, SIGCHLD) == -1) 283 err(1, NULL); 284 if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) 285 err(1, NULL); 286 287 for (;;) { 288 char *uri, *dst, *compdst; 289 unsigned int id; 290 pid_t pid; 291 int st; 292 293 pfd.events = 0; 294 pfd.events |= POLLIN; 295 if (msgq.queued) 296 pfd.events |= POLLOUT; 297 298 if (npending > 0 && nprocs < MAX_RSYNC_REQUESTS) { 299 TAILQ_FOREACH(s, &states, entry) { 300 if (s->pid == 0) { 301 s->pid = exec_rsync(prog, bind_addr, 302 s->uri, s->dst, s->compdst); 303 if (++nprocs >= MAX_RSYNC_REQUESTS) 304 break; 305 if (--npending == 0) 306 break; 307 } 308 } 309 } 310 311 if (ppoll(&pfd, 1, NULL, &oldmask) == -1) { 312 if (errno != EINTR) 313 err(1, "ppoll"); 314 315 /* 316 * If we've received an EINTR, it means that one 317 * of our children has exited and we can reap it 318 * and look up its identifier. 319 * Then we respond to the parent. 320 */ 321 322 while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) { 323 int ok = 1; 324 325 TAILQ_FOREACH(s, &states, entry) 326 if (s->pid == pid) 327 break; 328 if (s == NULL) 329 errx(1, "waitpid: %d unexpected", pid); 330 331 if (!WIFEXITED(st)) { 332 warnx("rsync %s terminated abnormally", 333 s->uri); 334 rc = 1; 335 ok = 0; 336 } else if (WEXITSTATUS(st) != 0) { 337 warnx("rsync %s failed", s->uri); 338 ok = 0; 339 } 340 341 b = io_new_buffer(); 342 io_simple_buffer(b, &s->id, sizeof(s->id)); 343 io_simple_buffer(b, &ok, sizeof(ok)); 344 io_close_buffer(&msgq, b); 345 346 rsync_free(s); 347 nprocs--; 348 } 349 if (pid == -1 && errno != ECHILD) 350 err(1, "waitpid"); 351 352 continue; 353 } 354 355 if (pfd.revents & POLLOUT) { 356 switch (msgbuf_write(&msgq)) { 357 case 0: 358 errx(1, "write: connection closed"); 359 case -1: 360 err(1, "write"); 361 } 362 } 363 364 /* connection closed */ 365 if (pfd.revents & POLLHUP) 366 break; 367 368 if (!(pfd.revents & POLLIN)) 369 continue; 370 371 b = io_buf_read(fd, &inbuf); 372 if (b == NULL) 373 continue; 374 375 /* Read host and module. */ 376 io_read_buf(b, &id, sizeof(id)); 377 io_read_str(b, &dst); 378 io_read_str(b, &compdst); 379 io_read_str(b, &uri); 380 381 ibuf_free(b); 382 383 if (dst != NULL) { 384 rsync_new(id, uri, dst, compdst); 385 npending++; 386 } else { 387 TAILQ_FOREACH(s, &states, entry) 388 if (s->id == id) 389 break; 390 if (s != NULL) { 391 if (s->pid != 0) 392 kill(s->pid, SIGTERM); 393 else 394 rsync_free(s); 395 } 396 } 397 } 398 399 /* No need for these to be hanging around. */ 400 TAILQ_FOREACH_SAFE(s, &states, entry, ns) { 401 if (s->pid != 0) 402 kill(s->pid, SIGTERM); 403 rsync_free(s); 404 } 405 406 msgbuf_clear(&msgq); 407 exit(rc); 408 } 409