1 /* $OpenBSD: receiver.c,v 1.33 2024/05/21 05:00:48 jsg Exp $ */ 2 3 /* 4 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 5 * Copyright (c) 2019 Florian Obser <florian@openbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 #include <sys/mman.h> 20 #include <sys/stat.h> 21 22 #include <assert.h> 23 #include <err.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <inttypes.h> 27 #include <math.h> 28 #include <poll.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <time.h> 33 #include <unistd.h> 34 35 #include "extern.h" 36 37 enum pfdt { 38 PFD_SENDER_IN = 0, /* input from the sender */ 39 PFD_UPLOADER_IN, /* uploader input from a local file */ 40 PFD_DOWNLOADER_IN, /* downloader input from a local file */ 41 PFD_SENDER_OUT, /* output to the sender */ 42 PFD__MAX 43 }; 44 45 int 46 rsync_set_metadata(struct sess *sess, int newfile, 47 int fd, const struct flist *f, const char *path) 48 { 49 uid_t uid = (uid_t)-1; 50 gid_t gid = (gid_t)-1; 51 mode_t mode; 52 struct timespec ts[2]; 53 54 /* Conditionally adjust file modification time. */ 55 56 if (sess->opts->preserve_times) { 57 ts[0].tv_nsec = UTIME_NOW; 58 ts[1].tv_sec = f->st.mtime; 59 ts[1].tv_nsec = 0; 60 if (futimens(fd, ts) == -1) { 61 ERR("%s: futimens", path); 62 return 0; 63 } 64 LOG4("%s: updated date", f->path); 65 } 66 67 /* 68 * Conditionally adjust identifiers. 69 * If we have an EPERM, report it but continue on: this just 70 * means that we're mapping into an unknown (or disallowed) 71 * group identifier. 72 */ 73 if (getuid() == 0 && sess->opts->preserve_uids) 74 uid = f->st.uid; 75 if (sess->opts->preserve_gids) 76 gid = f->st.gid; 77 78 mode = f->st.mode; 79 if (uid != (uid_t)-1 || gid != (gid_t)-1) { 80 if (fchown(fd, uid, gid) == -1) { 81 if (errno != EPERM) { 82 ERR("%s: fchown", path); 83 return 0; 84 } 85 if (getuid() == 0) 86 WARNX("%s: identity unknown or not available " 87 "to user.group: %u.%u", f->path, uid, gid); 88 } else 89 LOG4("%s: updated uid and/or gid", f->path); 90 mode &= ~(S_ISTXT | S_ISUID | S_ISGID); 91 } 92 93 /* Conditionally adjust file permissions. */ 94 95 if (newfile || sess->opts->preserve_perms) { 96 if (fchmod(fd, mode) == -1) { 97 ERR("%s: fchmod", path); 98 return 0; 99 } 100 LOG4("%s: updated permissions", f->path); 101 } 102 103 return 1; 104 } 105 106 int 107 rsync_set_metadata_at(struct sess *sess, int newfile, int rootfd, 108 const struct flist *f, const char *path) 109 { 110 uid_t uid = (uid_t)-1; 111 gid_t gid = (gid_t)-1; 112 mode_t mode; 113 struct timespec ts[2]; 114 115 /* Conditionally adjust file modification time. */ 116 117 if (sess->opts->preserve_times && 118 !(S_ISLNK(f->st.mode) && sess->opts->ignore_link_times)) { 119 ts[0].tv_nsec = UTIME_NOW; 120 ts[1].tv_sec = f->st.mtime; 121 ts[1].tv_nsec = 0; 122 if (utimensat(rootfd, path, ts, AT_SYMLINK_NOFOLLOW) == -1) { 123 ERR("%s: utimensat", path); 124 return 0; 125 } 126 LOG4("%s: updated date", f->path); 127 } 128 129 /* 130 * Conditionally adjust identifiers. 131 * If we have an EPERM, report it but continue on: this just 132 * means that we're mapping into an unknown (or disallowed) 133 * group identifier. 134 */ 135 if (getuid() == 0 && sess->opts->preserve_uids) 136 uid = f->st.uid; 137 if (sess->opts->preserve_gids) 138 gid = f->st.gid; 139 140 mode = f->st.mode; 141 if (uid != (uid_t)-1 || gid != (gid_t)-1) { 142 if (fchownat(rootfd, path, uid, gid, AT_SYMLINK_NOFOLLOW) == -1) { 143 if (errno != EPERM) { 144 ERR("%s: fchownat", path); 145 return 0; 146 } 147 if (getuid() == 0) 148 WARNX("%s: identity unknown or not available " 149 "to user.group: %u.%u", f->path, uid, gid); 150 } else 151 LOG4("%s: updated uid and/or gid", f->path); 152 mode &= ~(S_ISTXT | S_ISUID | S_ISGID); 153 } 154 155 /* Conditionally adjust file permissions. */ 156 157 if (newfile || sess->opts->preserve_perms) { 158 if (fchmodat(rootfd, path, mode, AT_SYMLINK_NOFOLLOW) == -1) { 159 ERR("%s: fchmodat", path); 160 return 0; 161 } 162 LOG4("%s: updated permissions", f->path); 163 } 164 165 return 1; 166 } 167 168 /* 169 * Pledges: unveil, unix, rpath, cpath, wpath, stdio, fattr, chown. 170 * Pledges (dry-run): -unix, -cpath, -wpath, -fattr, -chown. 171 */ 172 int 173 rsync_receiver(struct sess *sess, int fdin, int fdout, const char *root) 174 { 175 struct flist *fl = NULL, *dfl = NULL; 176 size_t i, flsz = 0, dflsz = 0; 177 char *tofree; 178 int rc = 0, dfd = -1, phase = 0, c; 179 int32_t ioerror; 180 struct pollfd pfd[PFD__MAX]; 181 struct download *dl = NULL; 182 struct upload *ul = NULL; 183 mode_t oumask; 184 185 if (pledge("stdio unix rpath wpath cpath dpath fattr chown getpw unveil", NULL) == -1) 186 err(ERR_IPC, "pledge"); 187 188 /* 189 * Create the path for our destination directory, if we're not 190 * in dry-run mode (which would otherwise crash w/the pledge). 191 * This uses our current umask: we might set the permissions on 192 * this directory in post_dir(). 193 */ 194 195 if (!sess->opts->dry_run) { 196 if ((tofree = strdup(root)) == NULL) 197 err(ERR_NOMEM, NULL); 198 if (mkpath(tofree) < 0) 199 err(ERR_FILE_IO, "%s: mkpath", tofree); 200 free(tofree); 201 } 202 203 /* 204 * Make our entire view of the file-system be limited to what's 205 * in the root directory. 206 * This prevents us from accidentally (or "under the influence") 207 * writing into other parts of the file-system. 208 */ 209 if (sess->opts->basedir[0]) { 210 /* 211 * XXX just unveil everything for read 212 * Could unveil each basedir or maybe a common path 213 * also the fact that relative path are relative to the 214 * root does not help. 215 */ 216 if (unveil("/", "r") == -1) 217 err(ERR_IPC, "%s: unveil", root); 218 } 219 220 if (unveil(root, "rwc") == -1) 221 err(ERR_IPC, "%s: unveil", root); 222 223 if (unveil(NULL, NULL) == -1) 224 err(ERR_IPC, "unveil"); 225 226 /* Client sends exclusions. */ 227 if (!sess->opts->server) 228 send_rules(sess, fdout); 229 230 /* Server receives exclusions if delete is on. */ 231 if (sess->opts->server && sess->opts->del) 232 recv_rules(sess, fdin); 233 234 /* 235 * Start by receiving the file list and our mystery number. 236 * These we're going to be touching on our local system. 237 */ 238 239 if (!flist_recv(sess, fdin, &fl, &flsz)) { 240 ERRX1("flist_recv"); 241 goto out; 242 } 243 244 /* The IO error is sent after the file list. */ 245 246 if (!io_read_int(sess, fdin, &ioerror)) { 247 ERRX1("io_read_int"); 248 goto out; 249 } else if (ioerror != 0) { 250 ERRX1("io_error is non-zero"); 251 goto out; 252 } 253 254 if (flsz == 0 && !sess->opts->server) { 255 WARNX("receiver has empty file list: exiting"); 256 rc = 1; 257 goto out; 258 } else if (!sess->opts->server) 259 LOG1("Transfer starting: %zu files", flsz); 260 261 LOG2("%s: receiver destination", root); 262 263 /* 264 * Disable umask() so we can set permissions fully. 265 * Then open the directory iff we're not in dry_run. 266 */ 267 268 oumask = umask(0); 269 270 if (!sess->opts->dry_run) { 271 dfd = open(root, O_RDONLY | O_DIRECTORY); 272 if (dfd == -1) 273 err(ERR_FILE_IO, "%s: open", root); 274 } 275 276 /* 277 * Begin by conditionally getting all files we have currently 278 * available in our destination. 279 */ 280 281 if (sess->opts->del && 282 sess->opts->recursive && 283 !flist_gen_dels(sess, root, &dfl, &dflsz, fl, flsz)) { 284 ERRX1("rsync_receiver"); 285 goto out; 286 } 287 288 /* If we have a local set, go for the deletion. */ 289 290 if (!flist_del(sess, dfd, dfl, dflsz)) { 291 ERRX1("flist_del"); 292 goto out; 293 } 294 295 /* Initialise poll events to listen from the sender. */ 296 297 pfd[PFD_SENDER_IN].fd = fdin; 298 pfd[PFD_UPLOADER_IN].fd = -1; 299 pfd[PFD_DOWNLOADER_IN].fd = -1; 300 pfd[PFD_SENDER_OUT].fd = fdout; 301 302 pfd[PFD_SENDER_IN].events = POLLIN; 303 pfd[PFD_UPLOADER_IN].events = POLLIN; 304 pfd[PFD_DOWNLOADER_IN].events = POLLIN; 305 pfd[PFD_SENDER_OUT].events = POLLOUT; 306 307 ul = upload_alloc(root, dfd, fdout, CSUM_LENGTH_PHASE1, fl, flsz, 308 oumask); 309 310 if (ul == NULL) { 311 ERRX1("upload_alloc"); 312 goto out; 313 } 314 315 dl = download_alloc(sess, fdin, fl, flsz, dfd); 316 if (dl == NULL) { 317 ERRX1("download_alloc"); 318 goto out; 319 } 320 321 LOG2("%s: ready for phase 1 data", root); 322 323 for (;;) { 324 if ((c = poll(pfd, PFD__MAX, poll_timeout)) == -1) { 325 ERR("poll"); 326 goto out; 327 } else if (c == 0) { 328 ERRX("poll: timeout"); 329 goto out; 330 } 331 332 for (i = 0; i < PFD__MAX; i++) 333 if (pfd[i].revents & (POLLERR|POLLNVAL)) { 334 ERRX("poll: bad fd"); 335 goto out; 336 } else if (pfd[i].revents & POLLHUP) { 337 ERRX("poll: hangup"); 338 goto out; 339 } 340 341 /* 342 * If we have a read event and we're multiplexing, we 343 * might just have error messages in the pipe. 344 * It's important to flush these out so that we don't 345 * clog the pipe. 346 * Unset our polling status if there's nothing that 347 * remains in the pipe. 348 */ 349 350 if (sess->mplex_reads && 351 (pfd[PFD_SENDER_IN].revents & POLLIN)) { 352 if (!io_read_flush(sess, fdin)) { 353 ERRX1("io_read_flush"); 354 goto out; 355 } else if (sess->mplex_read_remain == 0) 356 pfd[PFD_SENDER_IN].revents &= ~POLLIN; 357 } 358 359 360 /* 361 * We run the uploader if we have files left to examine 362 * (i < flsz) or if we have a file that we've opened and 363 * is read to mmap. 364 */ 365 366 if ((pfd[PFD_UPLOADER_IN].revents & POLLIN) || 367 (pfd[PFD_SENDER_OUT].revents & POLLOUT)) { 368 c = rsync_uploader(ul, 369 &pfd[PFD_UPLOADER_IN].fd, 370 sess, &pfd[PFD_SENDER_OUT].fd); 371 if (c < 0) { 372 ERRX1("rsync_uploader"); 373 goto out; 374 } 375 } 376 377 /* 378 * We need to run the downloader when we either have 379 * read events from the sender or an asynchronous local 380 * open is ready. 381 * XXX: we don't disable PFD_SENDER_IN like with the 382 * uploader because we might stop getting error 383 * messages, which will otherwise clog up the pipes. 384 */ 385 386 if ((pfd[PFD_SENDER_IN].revents & POLLIN) || 387 (pfd[PFD_DOWNLOADER_IN].revents & POLLIN)) { 388 c = rsync_downloader(dl, sess, 389 &pfd[PFD_DOWNLOADER_IN].fd); 390 if (c < 0) { 391 ERRX1("rsync_downloader"); 392 goto out; 393 } else if (c == 0) { 394 assert(phase == 0); 395 phase++; 396 LOG2("%s: receiver ready for phase 2 data", root); 397 break; 398 } 399 400 /* 401 * FIXME: if we have any errors during the 402 * download, most notably files getting out of 403 * sync between the send and the receiver, then 404 * here we should bump our checksum length and 405 * go into the second phase. 406 */ 407 } 408 } 409 410 /* Properly close us out by progressing through the phases. */ 411 412 if (phase == 1) { 413 if (!io_write_int(sess, fdout, -1)) { 414 ERRX1("io_write_int"); 415 goto out; 416 } 417 if (!io_read_int(sess, fdin, &ioerror)) { 418 ERRX1("io_read_int"); 419 goto out; 420 } 421 if (ioerror != -1) { 422 ERRX("expected phase ack"); 423 goto out; 424 } 425 } 426 427 /* 428 * Now all of our transfers are complete, so we can fix up our 429 * directory permissions. 430 */ 431 432 if (!rsync_uploader_tail(ul, sess)) { 433 ERRX1("rsync_uploader_tail"); 434 goto out; 435 } 436 437 /* Process server statistics and say good-bye. */ 438 439 if (!sess_stats_recv(sess, fdin)) { 440 ERRX1("sess_stats_recv"); 441 goto out; 442 } 443 if (!io_write_int(sess, fdout, -1)) { 444 ERRX1("io_write_int"); 445 goto out; 446 } 447 448 LOG2("receiver finished updating"); 449 rc = 1; 450 out: 451 if (dfd != -1) 452 close(dfd); 453 upload_free(ul); 454 download_free(dl); 455 flist_free(fl, flsz); 456 flist_free(dfl, dflsz); 457 return rc; 458 } 459