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
rsync_set_metadata(struct sess * sess,int newfile,int fd,const struct flist * f,const char * path)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
rsync_set_metadata_at(struct sess * sess,int newfile,int rootfd,const struct flist * f,const char * path)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
rsync_receiver(struct sess * sess,int fdin,int fdout,const char * root)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