1 /* $OpenBSD: rsync.c,v 1.24 2021/04/19 17:04:35 deraadt 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 <assert.h> 23 #include <err.h> 24 #include <errno.h> 25 #include <poll.h> 26 #include <resolv.h> 27 #include <signal.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 #include <imsg.h> 33 34 #include "extern.h" 35 36 /* 37 * A running rsync process. 38 * We can have multiple of these simultaneously and need to keep track 39 * of which process maps to which request. 40 */ 41 struct rsyncproc { 42 char *uri; /* uri of this rsync proc */ 43 size_t id; /* identity of request */ 44 pid_t pid; /* pid of process or 0 if unassociated */ 45 }; 46 47 /* 48 * Return the base of a rsync URI (rsync://hostname/module). The 49 * caRepository provided by the RIR CAs point deeper than they should 50 * which would result in many rsync calls for almost every subdirectory. 51 * This is inefficent so instead crop the URI to a common base. 52 * The returned string needs to be freed by the caller. 53 */ 54 char * 55 rsync_base_uri(const char *uri) 56 { 57 const char *host, *module, *rest; 58 char *base_uri; 59 60 /* Case-insensitive rsync URI. */ 61 if (strncasecmp(uri, "rsync://", 8) != 0) { 62 warnx("%s: not using rsync schema", uri); 63 return NULL; 64 } 65 66 /* Parse the non-zero-length hostname. */ 67 host = uri + 8; 68 69 if ((module = strchr(host, '/')) == NULL) { 70 warnx("%s: missing rsync module", uri); 71 return NULL; 72 } else if (module == host) { 73 warnx("%s: zero-length rsync host", uri); 74 return NULL; 75 } 76 77 /* The non-zero-length module follows the hostname. */ 78 module++; 79 if (*module == '\0') { 80 warnx("%s: zero-length rsync module", uri); 81 return NULL; 82 } 83 84 /* The path component is optional. */ 85 if ((rest = strchr(module, '/')) == NULL) { 86 if ((base_uri = strdup(uri)) == NULL) 87 err(1, NULL); 88 return base_uri; 89 } else if (rest == module) { 90 warnx("%s: zero-length module", uri); 91 return NULL; 92 } 93 94 if ((base_uri = strndup(uri, rest - uri)) == NULL) 95 err(1, NULL); 96 return base_uri; 97 } 98 99 static void 100 proc_child(int signal) 101 { 102 103 /* Nothing: just discard. */ 104 } 105 106 /* 107 * Process used for synchronising repositories. 108 * This simply waits to be told which repository to synchronise, then 109 * does so. 110 * It then responds with the identifier of the repo that it updated. 111 * It only exits cleanly when fd is closed. 112 * FIXME: limit the number of simultaneous process. 113 * Currently, an attacker can trivially specify thousands of different 114 * repositories and saturate our system. 115 */ 116 void 117 proc_rsync(char *prog, char *bind_addr, int fd) 118 { 119 size_t i, idsz = 0; 120 int rc = 0; 121 struct pollfd pfd; 122 struct msgbuf msgq; 123 sigset_t mask, oldmask; 124 struct rsyncproc *ids = NULL; 125 126 pfd.fd = fd; 127 128 msgbuf_init(&msgq); 129 msgq.fd = fd; 130 131 /* 132 * Unveil the command we want to run. 133 * If this has a pathname component in it, interpret as a file 134 * and unveil the file directly. 135 * Otherwise, look up the command in our PATH. 136 */ 137 138 if (strchr(prog, '/') == NULL) { 139 const char *pp; 140 char *save, *cmd, *path; 141 struct stat stt; 142 143 if (getenv("PATH") == NULL) 144 errx(1, "PATH is unset"); 145 if ((path = strdup(getenv("PATH"))) == NULL) 146 err(1, NULL); 147 save = path; 148 while ((pp = strsep(&path, ":")) != NULL) { 149 if (*pp == '\0') 150 continue; 151 if (asprintf(&cmd, "%s/%s", pp, prog) == -1) 152 err(1, NULL); 153 if (lstat(cmd, &stt) == -1) { 154 free(cmd); 155 continue; 156 } else if (unveil(cmd, "x") == -1) 157 err(1, "%s: unveil", cmd); 158 free(cmd); 159 break; 160 } 161 free(save); 162 } else if (unveil(prog, "x") == -1) 163 err(1, "%s: unveil", prog); 164 165 if (pledge("stdio proc exec", NULL) == -1) 166 err(1, "pledge"); 167 168 /* Initialise retriever for children exiting. */ 169 170 if (sigemptyset(&mask) == -1) 171 err(1, NULL); 172 if (signal(SIGCHLD, proc_child) == SIG_ERR) 173 err(1, NULL); 174 if (sigaddset(&mask, SIGCHLD) == -1) 175 err(1, NULL); 176 if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) 177 err(1, NULL); 178 179 for (;;) { 180 char *uri = NULL, *dst = NULL; 181 ssize_t ssz; 182 size_t id; 183 pid_t pid; 184 int st; 185 186 pfd.events = POLLIN; 187 if (msgq.queued) 188 pfd.events |= POLLOUT; 189 190 if (ppoll(&pfd, 1, NULL, &oldmask) == -1) { 191 if (errno != EINTR) 192 err(1, "ppoll"); 193 194 /* 195 * If we've received an EINTR, it means that one 196 * of our children has exited and we can reap it 197 * and look up its identifier. 198 * Then we respond to the parent. 199 */ 200 201 while ((pid = waitpid(WAIT_ANY, &st, WNOHANG)) > 0) { 202 struct ibuf *b; 203 int ok = 1; 204 205 for (i = 0; i < idsz; i++) 206 if (ids[i].pid == pid) 207 break; 208 assert(i < idsz); 209 210 if (!WIFEXITED(st)) { 211 warnx("rsync %s terminated abnormally", 212 ids[i].uri); 213 rc = 1; 214 ok = 0; 215 } else if (WEXITSTATUS(st) != 0) { 216 warnx("rsync %s failed", ids[i].uri); 217 ok = 0; 218 } 219 220 b = ibuf_open(sizeof(size_t) + sizeof(ok)); 221 if (b == NULL) 222 err(1, NULL); 223 io_simple_buffer(b, &ids[i].id, sizeof(size_t)); 224 io_simple_buffer(b, &ok, sizeof(ok)); 225 ibuf_close(&msgq, b); 226 227 free(ids[i].uri); 228 ids[i].uri = NULL; 229 ids[i].pid = 0; 230 ids[i].id = 0; 231 } 232 if (pid == -1 && errno != ECHILD) 233 err(1, "waitpid"); 234 continue; 235 } 236 237 if (pfd.revents & POLLOUT) { 238 switch (msgbuf_write(&msgq)) { 239 case 0: 240 errx(1, "write: connection closed"); 241 case -1: 242 err(1, "write"); 243 } 244 } 245 246 if (!(pfd.revents & POLLIN)) 247 continue; 248 249 /* 250 * Read til the parent exits. 251 * That will mean that we can safely exit. 252 */ 253 254 if ((ssz = read(fd, &id, sizeof(size_t))) == -1) 255 err(1, "read"); 256 if (ssz == 0) 257 break; 258 259 /* Read host and module. */ 260 261 io_str_read(fd, &dst); 262 io_str_read(fd, &uri); 263 assert(dst); 264 assert(uri); 265 266 /* Run process itself, wait for exit, check error. */ 267 268 if ((pid = fork()) == -1) 269 err(1, "fork"); 270 271 if (pid == 0) { 272 char *args[32]; 273 274 if (pledge("stdio exec", NULL) == -1) 275 err(1, "pledge"); 276 i = 0; 277 args[i++] = (char *)prog; 278 args[i++] = "-rt"; 279 args[i++] = "--no-motd"; 280 args[i++] = "--timeout"; 281 args[i++] = "180"; 282 if (bind_addr != NULL) { 283 args[i++] = "--address"; 284 args[i++] = (char *)bind_addr; 285 } 286 args[i++] = uri; 287 args[i++] = dst; 288 args[i] = NULL; 289 /* XXX args overflow not prevented */ 290 execvp(args[0], args); 291 err(1, "%s: execvp", prog); 292 } 293 294 /* Augment the list of running processes. */ 295 296 for (i = 0; i < idsz; i++) 297 if (ids[i].pid == 0) 298 break; 299 if (i == idsz) { 300 ids = reallocarray(ids, idsz + 1, sizeof(*ids)); 301 if (ids == NULL) 302 err(1, NULL); 303 idsz++; 304 } 305 306 ids[i].id = id; 307 ids[i].pid = pid; 308 ids[i].uri = uri; 309 310 /* Clean up temporary values. */ 311 312 free(dst); 313 } 314 315 /* No need for these to be hanging around. */ 316 for (i = 0; i < idsz; i++) 317 if (ids[i].pid > 0) { 318 kill(ids[i].pid, SIGTERM); 319 free(ids[i].uri); 320 } 321 322 msgbuf_clear(&msgq); 323 free(ids); 324 exit(rc); 325 } 326