/* * Routines only used by the receiving process. * * Copyright (C) 1996-2000 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2003-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, visit the http://fsf.org website. */ #include "rsync.h" #include "inums.h" extern int dry_run; extern int do_xfers; extern int am_root; extern int am_daemon; extern int am_server; extern int inc_recurse; extern int log_before_transfer; extern int stdout_format_has_i; extern int logfile_format_has_i; extern int want_xattr_optim; extern int csum_length; extern int read_batch; extern int write_batch; extern int batch_gen_fd; extern int protocol_version; extern int relative_paths; extern int preserve_hard_links; extern int preserve_perms; extern int write_devices; extern int preserve_xattrs; extern int do_fsync; extern int basis_dir_cnt; extern int make_backups; extern int cleanup_got_literal; extern int remove_source_files; extern int append_mode; extern int sparse_files; extern int preallocate_files; extern int keep_partial; extern int checksum_seed; extern int whole_file; extern int inplace; extern int inplace_partial; extern int allowed_lull; extern int delay_updates; extern BOOL want_progress_now; extern mode_t orig_umask; extern struct stats stats; extern char *tmpdir; extern char *partial_dir; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern char sender_file_sum[MAX_DIGEST_LEN]; extern struct file_list *cur_flist, *first_flist, *dir_flist; extern filter_rule_list daemon_filter_list; extern OFF_T preallocated_len; extern int fuzzy_basis; extern struct name_num_item *xfer_sum_nni; extern int xfer_sum_len; extern int use_secure_symlinks; extern int operator_path_resolve; static struct bitbag *delayed_bits = NULL; static int phase = 0, redoing = 0; static flist_ndx_list batch_redo_list; /* This is non-0 when we are updating the basis file or an identical copy: */ static int updating_basis_or_equiv; #define TMPNAME_SUFFIX ".XXXXXX" #define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1) #define MAX_UNIQUE_NUMBER 999999 #define MAX_UNIQUE_LOOP 100 /* Open a basis/output path that may legitimately be an operator-trusted * ABSOLUTE path -- e.g. an absolute --partial-dir ("a directory reserved for * partial-dir work") or --backup-dir. secure_relative_open() deliberately * rejects an absolute relpath, so feeding it the whole absolute partialptr * (with a NULL basedir) returns EINVAL: the basis fd is then -1, no basis is * mapped, and receive_data() omits every matched block from the whole-file * verification checksum -> a spurious "failed verification" that strands the * (correct) data in the partial-dir forever. * * The operator's directory is trusted; only the leaf basename is peer-supplied. * So when basedir is NULL and relpath is absolute, split it into its directory * (trusted) and leaf and confine just the leaf -- exactly how secure_relative_ * open already trusts an absolute basedir while O_NOFOLLOW-confining the leaf. * Anything else is a straight pass-through that preserves the strict contract. */ static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode) { extern int am_daemon, am_chrooted; extern unsigned int module_dirlen; /* "insecure links = yes": restore the 3.2.7 plain open so an operator/peer * alt-dest basis follows symlinks like legacy rsync, the same opt-out the * other daemon symlink sites honour. */ if (symlink_optout_allowed()) { if (basedir) { char fullpath[MAXPATHLEN]; if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) { errno = ENAMETOOLONG; return -1; } return do_open(fullpath, flags, mode); } return do_open(relpath, flags, mode); } /* A peer-supplied --partial-dir basis/staging path (operator_path_resolve set * by recv_files) may be absolute (module_dir-prefixed on a non-chroot daemon) * and traverse a symlink the secure_relative_open path can't confine: resolve * it with the ownership walk, which follows a uid0/euid-owned symlink but * refuses a foreign one AND (via abspath_excluded_by_module) refuses a target * the module's exclude hides -- closing the partial-dir exclude bypass. */ if (operator_path_resolve) { char fullpath[MAXPATHLEN]; const char *p = relpath; if (basedir) { if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) { errno = ENAMETOOLONG; return -1; } p = fullpath; } return open_no_attacker_symlinks(p, flags, mode); } /* The confined resolver is needed for the sanitizing daemon * (am_daemon && !am_chrooted) and for a /./ inner-module chroot * (am_chrooted && module_dirlen) -- in the latter the kernel chroot confines * only the outer path, so a peer-chosen alt-dest basis index (fnamecmp_type) * could otherwise reach an outside-inner-module file through a symlinked * parent. Local / remote-shell mode has no module boundary, and a plain * "use chroot = yes" makes the kernel root the boundary, so there an alt-dest * basis like --link-dest=../01 must resolve against the cwd as a bare open did * before the hardening (confining it would reject the legitimate sibling * "..", #915). The re-anchoring in secure_relative_open() covers the * in-module ".." climb for the inner-module case too. */ if (!am_daemon || (am_chrooted && !module_dirlen)) { if (basedir) { char fullpath[MAXPATHLEN]; if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) { errno = ENAMETOOLONG; return -1; } return do_open(fullpath, flags, mode); } return do_open(relpath, flags, mode); } if (!basedir && relpath && *relpath == '/') { const char *slash = strrchr(relpath, '/'); const char *leaf = slash + 1; char dirbuf[MAXPATHLEN]; const char *dir; if (slash == relpath) dir = "/"; else { size_t dlen = slash - relpath; if (dlen >= sizeof dirbuf) { errno = ENAMETOOLONG; return -1; } memcpy(dirbuf, relpath, dlen); dirbuf[dlen] = '\0'; dir = dirbuf; } return secure_relative_open(dir, leaf, flags, mode); } return secure_relative_open(basedir, relpath, flags, mode); } /* Keep the ownership policy for every attempt to open a one-inplace partial * output. In particular, Linux's protected_regular compatibility retries * must not downgrade an operator-path open to the ordinary path resolver. */ static int secure_recv_open(const char *path, int flags, mode_t mode, int owner_walk) { int fd, save = operator_path_resolve; if (owner_walk) operator_path_resolve = 1; fd = secure_basis_open(NULL, path, flags, mode); operator_path_resolve = save; return fd; } /* get_tmpname() - create a tmp filename for a given filename * * If a tmpdir is defined, use that as the directory to put it in. Otherwise, * the tmp filename is in the same directory as the given name. Note that * there may be no directory at all in the given name! * * The tmp filename is basically the given filename with a dot prepended, and * .XXXXXX appended (for mkstemp() to put its unique gunk in). We take care * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as * the basename basically becomes 8 characters longer. In such a case, the * original name is shortened sufficiently to make it all fit. * * If the make_unique arg is True, the XXXXXX string is replaced with a unique * string that doesn't exist at the time of the check. This is intended to be * used for creating hard links, symlinks, devices, and special files, since * normal files should be handled by mkstemp() for safety. * * Of course, the only reason the file is based on the original name is to * make it easier to figure out what purpose a temp file is serving when a * transfer is in progress. */ int get_tmpname(char *fnametmp, const char *fname, BOOL make_unique) { int maxname, length = 0; const char *f; char *suf; if (tmpdir) { /* Note: this can't overflow, so the return value is safe */ length = strlcpy(fnametmp, tmpdir, MAXPATHLEN - 2); fnametmp[length++] = '/'; } if ((f = strrchr(fname, '/')) != NULL) { ++f; if (!tmpdir) { length = f - fname; /* copy up to and including the slash */ strlcpy(fnametmp, fname, length + 1); } } else f = fname; if (!tmpdir) { /* using a tmpdir avoids the leading dot on our temp names */ if (*f == '.') /* avoid an extra leading dot for OS X's sake */ f++; fnametmp[length++] = '.'; } /* The maxname value is bufsize, and includes space for the '\0'. * NAME_MAX needs an extra -1 for the name's leading dot. */ maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN, NAME_MAX - 1 - TMPNAME_SUFFIX_LEN); if (maxname < 0) { rprintf(FERROR_XFER, "temporary filename too long: %s\n", fname); fnametmp[0] = '\0'; return 0; } if (maxname) { int added = strlcpy(fnametmp + length, f, maxname); if (added >= maxname) added = maxname - 1; suf = fnametmp + length + added; /* Trim any dangling high-bit chars if the first-trimmed char (if any) is * also a high-bit char, just in case we cut into a multi-byte sequence. * We are guaranteed to stop because of the leading '.' we added. */ if ((int)f[added] & 0x80) { while ((int)suf[-1] & 0x80) suf--; } /* trim one trailing dot before our suffix's dot */ if (suf[-1] == '.') suf--; } else suf = fnametmp + length - 1; /* overwrite the leading dot with suffix's dot */ if (make_unique) { static unsigned counter_limit; unsigned counter; if (!counter_limit) { counter_limit = (unsigned)getpid() + MAX_UNIQUE_LOOP; if (counter_limit > MAX_UNIQUE_NUMBER || counter_limit < MAX_UNIQUE_LOOP) counter_limit = MAX_UNIQUE_LOOP; } counter = counter_limit - MAX_UNIQUE_LOOP; /* This doesn't have to be very good because we don't need * to worry about someone trying to guess the values: all * a conflict will do is cause a device, special file, hard * link, or symlink to fail to be created. Also: avoid * using mktemp() due to gcc's annoying warning. */ while (1) { snprintf(suf, TMPNAME_SUFFIX_LEN+1, ".%d", counter); if (access(fnametmp, 0) < 0) break; if (++counter >= counter_limit) return 0; } } else memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1); return 1; } /* Opens a temporary file for writing. * Success: Writes name into fnametmp, returns fd. * Failure: Clobbers fnametmp, returns -1. * Calling cleanup_set() is the caller's job. */ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file) { int fd; mode_t added_perms; if (!get_tmpname(fnametmp, fname, False)) return -1; if (am_root < 0) { /* For --fake-super, the file must be useable by the copying * user, just like it would be for root. */ added_perms = S_IRUSR|S_IWUSR; } else { /* For a normal copy, we need to be able to tweak things like xattrs. */ added_perms = S_IWUSR; } /* We initially set the perms without the setuid/setgid bits or group * access to ensure that there is no race condition. They will be * correctly updated after the right owner and group info is set. * (Thanks to snabb@epipe.fi for pointing this out.) */ /* For any non-chrooted receiver (secure_relpath_active()), create the * temp file securely so a parent-symlink race can't redirect it. When * the temp lives in the entry's own dir (the common case, no --temp-dir) * use the cached held dir fd; otherwise fall back to secure_mkstemp. An * operator-supplied --temp-dir (tmpdir) gets the ownership-walk resolver * (it may legitimately point outside the tree); the deep-entry-dir fallback, * when the held-dirfd cache declines, gets the strict transfer-path one. */ if (secure_relpath_active()) { int dfd = held_dfd_for(fnametmp, file); if (dfd >= 0) { char *slash = strrchr(fnametmp, '/'); fd = do_mkstemp_atfd(dfd, slash ? slash + 1 : fnametmp, (file->mode|added_perms) & INITACCESSPERMS); } else fd = secure_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS, tmpdir != NULL); } else fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS); #if 0 /* In most cases parent directories will already exist because their * information should have been previously transferred, but that may * not be the case with -R */ if (fd == -1 && relative_paths && errno == ENOENT && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) { /* Get back to name with XXXXXX in it. */ get_tmpname(fnametmp, fname, False); fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS); } #endif if (fd == -1) { rsyserr(FERROR_XFER, errno, "mkstemp %s failed", full_fname(fnametmp)); return -1; } return fd; } static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, const char *fname, int fd, struct file_struct *file, int inplace_sizing) { static char file_sum1[MAX_DIGEST_LEN]; struct map_struct *mapbuf; struct sum_struct sum; int32 len; OFF_T total_size = F_LENGTH(file); OFF_T offset = 0; OFF_T offset2; char *data; int32 i; char *map = NULL; #ifdef SUPPORT_PREALLOCATION if (preallocate_files && fd != -1 && total_size > 0 && (!inplace_sizing || total_size > size_r)) { /* Try to preallocate enough space for file's eventual length. Can * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ if ((preallocated_len = do_fallocate(fd, 0, total_size)) < 0) rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(fname)); } else #endif if (inplace_sizing) { #ifdef HAVE_FTRUNCATE /* The most compatible way to create a sparse file is to start with no length. */ if (sparse_files > 0 && whole_file && fd >= 0 && do_ftruncate(fd, 0) == 0) preallocated_len = 0; else #endif preallocated_len = size_r; } else preallocated_len = 0; read_sum_head(f_in, &sum); if (fd_r >= 0 && size_r > 0) { int32 read_size = MAX(sum.blength * 2, 16*1024); mapbuf = map_file(fd_r, size_r, read_size, sum.blength); if (DEBUG_GTE(DELTASUM, 2)) { rprintf(FINFO, "recv mapped %s of size %s\n", fname_r, big_num(size_r)); } } else mapbuf = NULL; sum_init(xfer_sum_nni, checksum_seed); if (append_mode > 0) { OFF_T j; sum.flength = (OFF_T)sum.count * sum.blength; if (sum.remainder) sum.flength -= sum.blength - sum.remainder; if (append_mode == 2 && mapbuf) { for (j = CHUNK_SIZE; j < sum.flength; j += CHUNK_SIZE) { if (INFO_GTE(PROGRESS, 1)) show_progress(offset, total_size); sum_update(map_ptr(mapbuf, offset, CHUNK_SIZE), CHUNK_SIZE); offset = j; } if (offset < sum.flength) { int32 len = (int32)(sum.flength - offset); if (INFO_GTE(PROGRESS, 1)) show_progress(offset, total_size); sum_update(map_ptr(mapbuf, offset, len), len); } } offset = sum.flength; if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) { rsyserr(FERROR_XFER, errno, "lseek of %s returned %s, not %s", full_fname(fname), big_num(j), big_num(offset)); exit_cleanup(RERR_FILEIO); } } while (1) { data = NULL; i = recv_token(f_in, &data); if (i == 0) break; if (INFO_GTE(PROGRESS, 1)) show_progress(offset, total_size); if (allowed_lull) maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER); if (i > 0) { if (!data) { rprintf(FERROR, "Invalid literal token with no data [%s]\n", who_am_i()); exit_cleanup(RERR_PROTOCOL); } if (DEBUG_GTE(DELTASUM, 3)) { rprintf(FINFO,"data recv %d at %s\n", i, big_num(offset)); } stats.literal_data += i; cleanup_got_literal = 1; sum_update(data, i); if (fd != -1 && write_file(fd, 0, offset, data, i) != i) goto report_write_error; offset += i; continue; } i = -(i+1); if (i < 0 || i >= sum.count) { rprintf(FERROR, "Invalid block index %d (count=%ld) [%s]\n", i, (long)sum.count, who_am_i()); exit_cleanup(RERR_PROTOCOL); } offset2 = i * (OFF_T)sum.blength; len = sum.blength; if (i == (int)sum.count-1 && sum.remainder != 0) len = sum.remainder; stats.matched_data += len; /* A block match with no mapped basis is a protocol inconsistency * ONLY when we are actually producing output (fd != -1): the * generator told the sender a basis existed but the receiver could * not open it, so honoring the match would silently omit these * bytes from the verification checksum (a spurious failure) or * leave a hole in the output. Fail cleanly in that case. * * On the DISCARD path (fd == -1, fname == NULL) there is no output * and no verification: discard_receive_data() deliberately drains a * delta the receiver never intends to write (basis fstat failed, * basis is a directory, output open failed, batch skip, ...). The * sender does not know the data is being discarded and streams an * ordinary delta, so a match token here is NORMAL protocol, not * malformed. Absorb it benignly (advance the offset and continue), * as the pre-existing "if (mapbuf)" guards did before this check was * added in 31fbb17d -- erroring would wrongly break legitimate * transfers, and full_fname(fname) with fname==NULL would * dereference NULL (a receiver crash on a normal transfer). */ if (!mapbuf) { if (fd != -1) { rprintf(FERROR, "got a block match with no basis file for %s [%s]\n", full_fname(fname), who_am_i()); exit_cleanup(RERR_PROTOCOL); } offset += len; continue; } if (DEBUG_GTE(DELTASUM, 3)) { rprintf(FINFO, "chunk[%d] of size %ld at %s offset=%s%s\n", i, (long)len, big_num(offset2), big_num(offset), updating_basis_or_equiv && offset == offset2 ? " (seek)" : ""); } if (mapbuf) { map = map_ptr(mapbuf,offset2,len); see_token(map, len); sum_update(map, len); } if (updating_basis_or_equiv) { if (offset == offset2 && fd != -1) { if (skip_matched(fd, offset, map, len) < 0) goto report_write_error; offset += len; continue; } } if (fd != -1 && map && write_file(fd, 0, offset, map, len) != (int)len) goto report_write_error; offset += len; } if (fd != -1 && offset > 0) { if (sparse_files > 0) { if (sparse_end(fd, offset, updating_basis_or_equiv) != 0) goto report_write_error; } else if (flush_write_file(fd) < 0) { report_write_error: rsyserr(FERROR_XFER, errno, "write failed on %s", full_fname(fname)); exit_cleanup(RERR_FILEIO); } } #ifdef HAVE_FTRUNCATE /* inplace: New data could be shorter than old data. * preallocate_files: total_size could have been an overestimate. * Cut off any extra preallocated zeros from dest file. */ if ((inplace_sizing || preallocated_len > offset) && fd != -1 && !IS_DEVICE(file->mode)) { if (do_ftruncate(fd, offset) < 0) rsyserr(FERROR_XFER, errno, "ftruncate failed on %s", full_fname(fname)); } #endif if (INFO_GTE(PROGRESS, 1)) end_progress(total_size); sum_end(file_sum1); if (do_fsync && fd != -1 && fsync(fd) != 0) { rsyserr(FERROR, errno, "fsync failed on %s", full_fname(fname)); exit_cleanup(RERR_FILEIO); } if (mapbuf) unmap_file(mapbuf); read_buf(f_in, sender_file_sum, xfer_sum_len); if (DEBUG_GTE(DELTASUM, 2)) rprintf(FINFO,"got file_sum\n"); if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0) return 0; return 1; } static void discard_receive_data(int f_in, struct file_struct *file) { receive_data(f_in, NULL, -1, 0, NULL, -1, file, 0); } static void handle_delayed_updates(char *local_name) { char *fname, *partialptr; int ndx; for (ndx = -1; (ndx = bitbag_next_bit(delayed_bits, ndx)) >= 0; ) { struct file_struct *file = cur_flist->files[ndx]; fname = local_name ? local_name : f_name(file, NULL); if ((partialptr = partial_dir_fname(fname)) != NULL) { if (make_backups > 0 && !make_backup(fname, False)) continue; if (DEBUG_GTE(RECV, 1)) { rprintf(FINFO, "renaming %s to %s\n", partialptr, fname); } /* We don't use robust_rename() here because the * partial-dir must be on the same drive. Resolve the * --partial-dir source through the exclude-aware ownership * walk so a symlinked partial-dir can't move a file out of * an excluded subtree. */ int rret; operator_path_resolve = 1; rret = do_rename_at(partialptr, fname); operator_path_resolve = 0; if (rret < 0) { rsyserr(FERROR_XFER, errno, "rename failed for %s (from %s)", full_fname(fname), partialptr); } else { if (remove_source_files || (preserve_hard_links && F_IS_HLINKED(file))) send_msg_success(fname, ndx); handle_partial_dir(partialptr, PDIR_DELETE); } } } } static void no_batched_update(int ndx, BOOL is_redo) { struct file_list *flist = flist_for_ndx(ndx, "no_batched_update"); struct file_struct *file; if (ndx < flist->ndx_start) exit_cleanup(RERR_PROTOCOL); file = flist->files[ndx - flist->ndx_start]; rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n", is_redo ? " resend of" : "", f_name(file, NULL)); if (inc_recurse && !dry_run) send_msg_int(MSG_NO_SEND, ndx); } static int we_want_redo(int desired_ndx) { static int redo_ndx = -1; while (redo_ndx < desired_ndx) { if (redo_ndx >= 0) no_batched_update(redo_ndx, True); if ((redo_ndx = flist_ndx_pop(&batch_redo_list)) < 0) return 0; } if (redo_ndx == desired_ndx) { redo_ndx = -1; return 1; } return 0; } static int gen_wants_ndx(int desired_ndx, int flist_num) { static int next_ndx = -1; static int done_cnt = 0; static BOOL got_eof = False; if (got_eof) return 0; /* TODO: integrate gen-reading I/O into perform_io() so this is not needed? */ io_flush(FULL_FLUSH); while (next_ndx < desired_ndx) { if (inc_recurse && flist_num <= done_cnt) return 0; if (next_ndx >= 0) no_batched_update(next_ndx, False); if ((next_ndx = read_int(batch_gen_fd)) < 0) { if (inc_recurse) { done_cnt++; continue; } got_eof = True; return 0; } } if (next_ndx == desired_ndx) { next_ndx = -1; return 1; } return 0; } /** * main routine for receiver process. * * Receiver process runs on the same host as the generator process. */ int recv_files(int f_in, int f_out, char *local_name) { int fd1,fd2; STRUCT_STAT st; int iflags, xlen; char *fname, fbuf[MAXPATHLEN]; char xname[MAXPATHLEN]; char *fnametmp, fnametmpbuf[MAXPATHLEN]; char *fnamecmp, *partialptr; char fnamecmpbuf[MAXPATHLEN]; uchar fnamecmp_type; struct file_struct *file; int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i; enum logcode log_code = log_before_transfer ? FLOG : FINFO; int max_phase = protocol_version >= 29 ? 2 : 1; int dflt_perms = (ACCESSPERMS & ~orig_umask); #ifdef SUPPORT_ACLS const char *parent_dirname = ""; #endif int ndx, recv_ok, one_inplace, write_to_device; mode_t write_devices_saved_mode = 0; if (DEBUG_GTE(RECV, 1)) rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used); if (delay_updates) delayed_bits = bitbag_create(cur_flist->used + 1); if (whole_file < 0) whole_file = 0; progress_init(); while (1) { const char *basedir = NULL; cleanup_disable(); /* This call also sets cur_flist. */ ndx = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen); if (ndx == NDX_DONE) { if (!am_server && cur_flist) { set_current_file_index(NULL, 0); if (INFO_GTE(PROGRESS, 2)) end_progress(0); } if (inc_recurse && first_flist) { if (read_batch) { ndx = first_flist->used + first_flist->ndx_start; gen_wants_ndx(ndx, first_flist->flist_num); } flist_free(first_flist); if (first_flist) continue; } else if (read_batch && first_flist) { ndx = first_flist->used; gen_wants_ndx(ndx, first_flist->flist_num); } if (++phase > max_phase) break; if (DEBUG_GTE(RECV, 1)) rprintf(FINFO, "recv_files phase=%d\n", phase); if (phase == 2 && delay_updates) handle_delayed_updates(local_name); write_int(f_out, NDX_DONE); continue; } if (ndx - cur_flist->ndx_start >= 0) file = cur_flist->files[ndx - cur_flist->ndx_start]; else if (cur_flist->parent_ndx < 0) exit_cleanup(RERR_PROTOCOL); else file = dir_flist->files[cur_flist->parent_ndx]; if (!F_IS_ACTIVE(file)) { /* A peer that sends duplicate file-list entries gets * one of them clear_file()'d by flist_sort_and_clean(); * referencing that slot here yields fname == NULL and * a crash in the first deref (daemon filter check, * set_file_attrs → full_fname, …). */ rprintf(FERROR, "rsync: refusing transfer of cleared file index %d\n", ndx); exit_cleanup(RERR_PROTOCOL); } fname = local_name ? local_name : f_name(file, fbuf); if (DEBUG_GTE(RECV, 1)) rprintf(FINFO, "recv_files(%s)\n", fname); if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0')) { int filt_flags = S_ISDIR(file->mode) ? NAME_IS_DIR : NAME_IS_FILE; if (check_filter(&daemon_filter_list, FLOG, fname, filt_flags) < 0) { rprintf(FERROR, "ERROR: rejecting file transfer request for daemon excluded file: %s\n", fname); exit_cleanup(RERR_PROTOCOL); } } #ifdef SUPPORT_XATTRS if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))) recv_xattr_request(file, f_in); #endif if (!(iflags & ITEM_TRANSFER)) { maybe_log_item(file, iflags, itemizing, xname); #ifdef SUPPORT_XATTRS if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && do_xfers && !BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE)) set_file_attrs(fname, file, NULL, fname, 0); #endif if (iflags & ITEM_IS_NEW) { stats.created_files++; if (S_ISREG(file->mode)) { /* Nothing further to count. */ } else if (S_ISDIR(file->mode)) stats.created_dirs++; #ifdef SUPPORT_LINKS else if (S_ISLNK(file->mode)) stats.created_symlinks++; #endif else if (IS_DEVICE(file->mode)) stats.created_devices++; else stats.created_specials++; } continue; } if (phase == 2) { rprintf(FERROR, "got transfer request in phase 2 [%s]\n", who_am_i()); exit_cleanup(RERR_PROTOCOL); } if (file->flags & FLAG_FILE_SENT) { if (csum_length == SHORT_SUM_LENGTH) { if (keep_partial && !partial_dir) make_backups = -make_backups; /* prevents double backup */ if (append_mode) sparse_files = -sparse_files; append_mode = -append_mode; csum_length = SUM_LENGTH; redoing = 1; } } else { if (csum_length != SHORT_SUM_LENGTH) { if (keep_partial && !partial_dir) make_backups = -make_backups; if (append_mode) sparse_files = -sparse_files; append_mode = -append_mode; csum_length = SHORT_SUM_LENGTH; redoing = 0; } if (iflags & ITEM_IS_NEW) stats.created_files++; } if (!am_server) set_current_file_index(file, ndx); stats.xferred_files++; stats.total_transferred_size += F_LENGTH(file); cleanup_got_literal = 0; if (read_batch) { int wanted = redoing ? we_want_redo(ndx) : gen_wants_ndx(ndx, cur_flist->flist_num); if (!wanted) { rprintf(FINFO, "(Skipping batched update for%s \"%s\")\n", redoing ? " resend of" : "", fname); discard_receive_data(f_in, file); file->flags |= FLAG_FILE_SENT; continue; } } remember_initial_stats(); if (!do_xfers) { /* log the transfer */ log_item(FCLIENT, file, iflags, NULL); if (read_batch) discard_receive_data(f_in, file); continue; } if (write_batch < 0) { log_item(FCLIENT, file, iflags, NULL); if (!am_server) discard_receive_data(f_in, file); if (inc_recurse) send_msg_success(fname, ndx); continue; } partialptr = partial_dir ? partial_dir_fname(fname) : fname; if (protocol_version >= 29) { switch (fnamecmp_type) { case FNAMECMP_FNAME: fnamecmp = fname; break; case FNAMECMP_PARTIAL_DIR: fnamecmp = partialptr; break; case FNAMECMP_BACKUP: fnamecmp = get_backup_name(fname); break; case FNAMECMP_FUZZY: if (fuzzy_basis == 0) { rprintf(FERROR_XFER, "rsync: refusing malicious fuzzy operation for %s\n", xname); exit_cleanup(RERR_PROTOCOL); } if (file->dirname) { basedir = file->dirname; } fnamecmp = xname; break; default: if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) { fnamecmp_type -= FNAMECMP_FUZZY + 1; if (file->dirname) { pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], file->dirname); basedir = fnamecmpbuf; } else { basedir = basis_dir[fnamecmp_type]; } fnamecmp = xname; } else if (fnamecmp_type >= basis_dir_cnt) { rprintf(FERROR, "invalid basis_dir index: %d.\n", fnamecmp_type); exit_cleanup(RERR_PROTOCOL); } else { basedir = basis_dir[fnamecmp_type]; fnamecmp = fname; } break; } if (!fnamecmp || (daemon_filter_list.head && check_filter(&daemon_filter_list, FLOG, fnamecmp, 0) < 0)) { fnamecmp = fname; fnamecmp_type = FNAMECMP_FNAME; } } else { /* Reminder: --inplace && --partial-dir are never * enabled at the same time. */ if (inplace && make_backups > 0) { if (!(fnamecmp = get_backup_name(fname))) fnamecmp = fname; else fnamecmp_type = FNAMECMP_BACKUP; } else if (partial_dir && partialptr) fnamecmp = partialptr; else fnamecmp = fname; } /* Open the delta basis. When it lives in the entry's own dir (no * alternate basedir), read it via the held dir fd with O_NOFOLLOW: a * regular-file basis is never legitimately a symlink, and refusing a * planted one avoids both an escape and a basis-content info-leak. * Otherwise use secure_basis_open, which also tolerates an operator- * trusted absolute fnamecmp (e.g. an absolute --partial-dir basis). */ { int bdfd; if (fnamecmp_type == FNAMECMP_PARTIAL_DIR && fnamecmp && *fnamecmp != '/') { /* The relative partial path contains peer-derived directory * components. It is not an operator-trusted path as a whole. */ fd1 = secure_relative_open(NULL, fnamecmp, O_RDONLY, 0); } else if (!basedir && (bdfd = held_dfd_for(fnamecmp, file)) >= 0) { const char *slash; assert(fnamecmp != NULL); /* set on every path above */ slash = strrchr(fnamecmp, '/'); fd1 = do_open_atfd(bdfd, slash ? slash + 1 : fnamecmp, O_RDONLY, 0); } else { /* An operator-supplied basis -- a --partial-dir, or an * alt-dest basedir (--copy-dest/--compare-dest/--link-dest) -- * is a peer/operator path: resolve it with the exclude-aware * ownership walk so a flipped foreign-owned parent symlink can't * read (and feed back as delta) an out-of-tree / excluded file. * The walk still allows the legitimate "../sibling" basis (#915) * and the operator's own uid0/euid symlinks. A daemon keeps its * stronger confinement branch in secure_basis_open(), so only * route the alt-dest basedir read through the walk off-daemon. */ if ((basedir && !am_daemon) || fnamecmp_type == FNAMECMP_PARTIAL_DIR) operator_path_resolve = 1; fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0); operator_path_resolve = 0; } } if (fnamecmp_type == FNAMECMP_PARTIAL_DIR && fd1 == -1) { /* The sender may claim a partial basis even when the generator's * confined lookup rejected it. Drop that path as the delta basis * and in-place output target. A daemon that negotiated in-place * partial updates rejects the unsafe peer option; otherwise (a pre-30 * peer, where no in-place partial redirect is possible, or a pull * client) fall back to a safe no-basis update. */ if (am_daemon && inplace_partial) { rprintf(FERROR, "rsync: refusing unconfined partial basis for %s\n", fname); exit_cleanup(RERR_PROTOCOL); } fnamecmp = fname; fnamecmp_type = FNAMECMP_FNAME; } if (fd1 == -1 && protocol_version < 29) { if (fnamecmp != fname) { fnamecmp = fname; fnamecmp_type = FNAMECMP_FNAME; fd1 = do_open_nofollow(fnamecmp, O_RDONLY); } if (fd1 == -1 && basis_dir[0]) { /* pre-29 allowed only one alternate basis */ basedir = basis_dir[0]; fnamecmp = fname; fnamecmp_type = FNAMECMP_BASIS_DIR_LOW; if (!am_daemon) operator_path_resolve = 1; fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0); operator_path_resolve = 0; } } if (basedir) { // for the following code we need the full // path name as a single string pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basedir, fnamecmp); fnamecmp = fnamecmpbuf; } /* A peer's basis selector cannot enable direct output through a path * that the confined basis open did not validate. */ one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR && fd1 != -1; updating_basis_or_equiv = one_inplace || (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP)); if (fd1 == -1) { st.st_mode = 0; st.st_size = 0; } else if (do_fstat(fd1,&st) != 0) { rsyserr(FERROR_XFER, errno, "fstat %s failed", full_fname(fnamecmp)); discard_receive_data(f_in, file); close(fd1); if (inc_recurse) send_msg_int(MSG_NO_SEND, ndx); continue; } if (fd1 != -1 && S_ISDIR(st.st_mode) && fnamecmp == fname) { /* this special handling for directories * wouldn't be necessary if robust_rename() * and the underlying robust_unlink could cope * with directories */ rprintf(FERROR_XFER, "recv_files: %s is a directory\n", full_fname(fnamecmp)); discard_receive_data(f_in, file); close(fd1); if (inc_recurse) send_msg_int(MSG_NO_SEND, ndx); continue; } write_to_device = write_devices && IS_DEVICE(st.st_mode); if (write_to_device) { if (fd1 != -1 && st.st_size == 0) st.st_size = get_device_size(fd1, fname); } else if (fd1 != -1 && !(S_ISREG(st.st_mode))) { close(fd1); fd1 = -1; } /* If we're not preserving permissions, change the file-list's * mode based on the local permissions and some heuristics. */ if (!preserve_perms) { int exists = fd1 != -1; #ifdef SUPPORT_ACLS const char *dn = file->dirname ? file->dirname : "."; if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) { dflt_perms = default_perms_for_dir(dn); parent_dirname = dn; } #endif file->mode = dest_mode(file->mode, st.st_mode, dflt_perms, exists); } /* We now check to see if we are writing the file "inplace" */ if (inplace || one_inplace) { fnametmp = one_inplace ? partialptr : fname; /* When use_secure_symlinks is on (non-chroot daemon), * use secure open to prevent symlink race attacks where an * attacker could switch a directory to a symlink between * path validation and file open. */ /* one_inplace stages into the operator/peer --partial-dir path: * resolve it with the ownership walk (exclude-aware) so it can't be * redirected through a symlink into an excluded subtree. */ if (secure_relpath_active()) fd2 = secure_recv_open(fnametmp, O_WRONLY|O_CREAT, 0600, one_inplace); else fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600); #ifdef linux if (fd2 == -1 && errno == EACCES) { /* Maybe the error was due to protected_regular setting? */ if (use_secure_symlinks || one_inplace) fd2 = secure_recv_open(fnametmp, O_WRONLY, 0600, one_inplace); else fd2 = do_open(fnametmp, O_WRONLY, 0600); } #endif if (fd2 == -1) { rsyserr(FERROR_XFER, errno, "open %s failed", full_fname(fnametmp)); } else if (updating_basis_or_equiv) cleanup_set(NULL, NULL, file, fd1, fd2); } else { fnametmp = fnametmpbuf; fd2 = open_tmpfile(fnametmp, fname, file); if (fd2 != -1) cleanup_set(fnametmp, partialptr, file, fd1, fd2); } if (fd2 == -1) { discard_receive_data(f_in, file); if (fd1 != -1) close(fd1); if (inc_recurse) send_msg_int(MSG_NO_SEND, ndx); continue; } /* log the transfer */ if (log_before_transfer) log_item(FCLIENT, file, iflags, NULL); else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1)) rprintf(FINFO, "%s\n", fname); /* --write-devices writes a regular source file's content into an * existing destination device. Flip file->mode to S_IFBLK just for * receive_data()'s ftruncate gate (!IS_DEVICE), then restore it right * after -- the file_struct was built as a regular file with no * DEV_EXTRA_CNT, so leaving it S_IFBLK would make set_stat_xattr's * F_RDEV_P() read past the allocation. Kept tight here (after the * pre-transfer log and the fd2 bail-out) so no continue skips the * restore and dest_mode() above ran on the real mode. */ if (write_to_device) { write_devices_saved_mode = file->mode; file->mode = S_IFBLK | (file->mode & ACCESSPERMS); } /* recv file data */ recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, fname, fd2, file, inplace || one_inplace); if (write_to_device) { file->mode = write_devices_saved_mode; write_devices_saved_mode = 0; } log_item(log_code, file, iflags, NULL); if (want_progress_now) instant_progress(fname); if (fd1 != -1) close(fd1); if (close(fd2) < 0) { rsyserr(FERROR, errno, "close failed on %s", full_fname(fnametmp)); exit_cleanup(RERR_FILEIO); } if ((recv_ok && (!delay_updates || !partialptr)) || inplace) { if (partialptr == fname) partialptr = NULL; if (!finish_transfer(fname, fnametmp, fnamecmp, partialptr, file, recv_ok, 1)) recv_ok = -1; else if (fnamecmp == partialptr) { if (!one_inplace) { /* Unlink the consumed --partial-dir basis through the * exclude-aware ownership walk (a symlinked partial-dir * must not delete a file in an excluded subtree). */ operator_path_resolve = 1; do_unlink_at(partialptr); operator_path_resolve = 0; } handle_partial_dir(partialptr, PDIR_DELETE); } } else if (keep_partial && partialptr && (!one_inplace || delay_updates)) { if (!handle_partial_dir(partialptr, PDIR_CREATE)) { rprintf(FERROR, "Unable to create partial-dir for %s -- discarding %s.\n", local_name ? local_name : f_name(file, NULL), recv_ok ? "completed file" : "partial file"); do_unlink_at(fnametmp); recv_ok = -1; } else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL, file, recv_ok, !partial_dir)) recv_ok = -1; else if (delay_updates && recv_ok) { bitbag_set_bit(delayed_bits, ndx); recv_ok = 2; } else partialptr = NULL; } else if (!one_inplace) do_unlink_at(fnametmp); cleanup_disable(); if (read_batch) file->flags |= FLAG_FILE_SENT; switch (recv_ok) { case 2: break; case 1: if (remove_source_files || inc_recurse || (preserve_hard_links && F_IS_HLINKED(file))) send_msg_success(fname, ndx); break; case 0: { enum logcode msgtype = redoing ? FERROR_XFER : FWARNING; if (msgtype == FERROR_XFER || INFO_GTE(NAME, 1) || stdout_format_has_i) { char *errstr, *redostr, *keptstr; if (!(keep_partial && partialptr) && !inplace) keptstr = "discarded"; else if (partial_dir) keptstr = "put into partial-dir"; else keptstr = "retained"; if (msgtype == FERROR_XFER) { errstr = "ERROR"; redostr = ""; } else { errstr = "WARNING"; redostr = read_batch ? " (may try again)" : " (will try again)"; } rprintf(msgtype, "%s: %s failed verification -- update %s%s.\n", errstr, local_name ? f_name(file, NULL) : fname, keptstr, redostr); } if (!redoing) { if (read_batch) flist_ndx_push(&batch_redo_list, ndx); send_msg_int(MSG_REDO, ndx); file->flags |= FLAG_FILE_SENT; } else if (inc_recurse) send_msg_int(MSG_NO_SEND, ndx); break; } case -1: if (inc_recurse) send_msg_int(MSG_NO_SEND, ndx); break; } } if (make_backups < 0) make_backups = -make_backups; if (phase == 2 && delay_updates) /* for protocol_version < 29 */ handle_delayed_updates(local_name); if (DEBUG_GTE(RECV, 1)) rprintf(FINFO,"recv_files finished\n"); return 0; }