*
* The stat structure pointed to by stp will contain information about the
* link or the referent as appropriate, if they exist. */
-static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
+static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf,
+ unsigned int *phys_depth_p)
{
#ifdef SUPPORT_LINKS
if (link_stat(path, stp, copy_dirlinks) < 0)
if (llen < 0)
return -1;
linkbuf[llen] = '\0';
- if (copy_unsafe_links && unsafe_symlink(linkbuf, path)) {
+ if (copy_unsafe_links && unsafe_symlink(linkbuf, *phys_depth_p)) {
if (INFO_GTE(SYMSAFE, 1)) {
rprintf(FINFO,"copying unsafe symlink \"%s\" -> \"%s\"\n",
path, linkbuf);
}
+ *phys_depth_p = 0;
return x_stat(path, stp, NULL);
}
if (munge_symlinks && am_sender && llen > SYMLINK_PREFIX_LEN
llen - SYMLINK_PREFIX_LEN + 1);
}
}
+ if (copy_unsafe_links && S_ISDIR(stp->st_mode))
+ (*phys_depth_p)++;
return 0;
#else
return x_stat(path, stp, NULL);
}
static void send_directory(int f, struct file_list *flist,
- char *fbuf, int len, int flags);
+ char *fbuf, int len, int flags, unsigned int phys_depth);
static const char *pathname, *orig_dir;
static int pathname_len;
/* Create a file_struct for a named file by reading its stat() information
* and performing extensive checks against global options.
*
+ * If we're the sender and --copy-unsafe-links is on, we want to consider a
+ * symlink unsafe if it leaves the subtree established by another unsafe
+ * symlink, but that can't be determined based on "fname". So the depth of
+ * real directories since the last unsafe symlink is passed as "phys_depth".
+ * In other cases, "phys_depth" is ignored.
+ *
* Returns a pointer to the new file struct, or NULL if there was an error
* or this file should be excluded.
*
* "io_error |= IOERR_GENERAL" to avoid deletion of the file from the
* destination if --delete is on. */
struct file_struct *make_file(const char *fname, struct file_list *flist,
- STRUCT_STAT *stp, int flags, int filter_level)
+ STRUCT_STAT *stp, int flags, int filter_level,
+ unsigned int phys_depth)
{
static char *lastdir;
static int lastdir_len = -1;
* dir, or a request to delete a specific file. */
st = *stp;
*linkname = '\0'; /* make IBM code checker happy */
- } else if (readlink_stat(thisname, &st, linkname) != 0) {
+ } else if (readlink_stat(thisname, &st, linkname, &phys_depth) != 0) {
int save_errno = errno;
/* See if file is excluded before reporting an error. */
if (filter_level != NO_FILTERS
extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
}
+ if (copy_unsafe_links && S_ISDIR(st.st_mode))
+ extra_len += EXTRA_LEN; // F_DIR_PHYS_DEPTH
+
#if EXTRA_ROUNDING > 0
if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
if (sender_keeps_checksum && S_ISREG(st.st_mode))
memcpy(F_SUM(file), tmp_sum, checksum_len);
+ if (copy_unsafe_links && S_ISDIR(st.st_mode))
+ F_DIR_PHYS_DEPTH(file) = phys_depth; /* as adjusted by readlink_stat */
+
if (unsort_ndx)
F_NDX(file) = stats.num_dirs;
static struct file_struct *send_file_name(int f, struct file_list *flist,
const char *fname, STRUCT_STAT *stp,
- int flags, int filter_level)
+ int flags, int filter_level,
+ unsigned int phys_depth)
{
struct file_struct *file;
- file = make_file(fname, flist, stp, flags, filter_level);
+ file = make_file(fname, flist, stp, flags, filter_level, phys_depth);
if (!file)
return NULL;
return;
}
save_filters = push_local_filters(fbuf, len);
- send_directory(f, flist, fbuf, len, flags);
+ send_directory(f, flist, fbuf, len, flags, F_DIR_PHYS_DEPTH(file));
pop_local_filters(save_filters);
fbuf[ol] = '\0';
if (is_dot_dir)
* might call this with f set to -2, which also indicates that local filter
* rules should be ignored. */
static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
- int flags)
+ int flags, unsigned int phys_depth)
{
struct dirent *di;
unsigned remainder;
continue;
}
- send_file_name(f, flist, fbuf, NULL, flags, filter_level);
+ send_file_name(f, flist, fbuf, NULL, flags, filter_level, phys_depth);
}
fbuf[len] = '\0';
}
}
+/* Used in a few places where we need to pass a phys_depth but can't easily
+ * determine what it should be. FIXME! */
+#define FIXME_path_depth path_depth
+
static void send_implied_dirs(int f, struct file_list *flist, char *fname,
char *start, char *limit, int flags, char name_type)
{
for (slash = start; (slash = strchr(slash+1, '/')) != NULL; ) {
*slash = '\0';
- file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS);
+ file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS,
+ FIXME_path_depth(fname));
depth++;
if (!inc_recurse && file && S_ISDIR(file->mode))
change_local_filter_dir(fname, strlen(fname), depth);
*slash = '/';
}
- file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS);
+ file = send_file_name(f, flist, fname, NULL, flags, ALL_FILTERS,
+ FIXME_path_depth(fname));
if (inc_recurse) {
if (file && !S_ISDIR(file->mode))
file = NULL;
}
filesystem_dev = st.st_dev;
}
- send_directory(f, flist, fbuf, dlen, flags);
+ send_directory(f, flist, fbuf, dlen, flags, F_DIR_PHYS_DEPTH(file));
}
if (!relative_paths)
interpret_stat_error(fbuf, True);
continue;
}
- send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR | flags, ALL_FILTERS);
+ send_file_name(f, flist, fbuf, &st, FLAG_TOP_DIR | flags, ALL_FILTERS,
+ FIXME_path_depth(fbuf));
} else
- send_file_name(f, flist, fbuf, NULL, FLAG_TOP_DIR | flags, ALL_FILTERS);
+ send_file_name(f, flist, fbuf, NULL, FLAG_TOP_DIR | flags, ALL_FILTERS,
+ FIXME_path_depth(fbuf));
}
free(relname_list);
if (*fn == '.' && fn[1] == '/' && !implied_dot_dir) {
send_file_name(f, flist, ".", NULL,
(flags | FLAG_IMPLIED_DIR) & ~FLAG_CONTENT_DIR,
- ALL_FILTERS);
+ ALL_FILTERS, 0);
implied_dot_dir = 1;
}
len = clean_fname(fn, CFN_KEEP_TRAILING_SLASH
struct file_struct *file;
file = send_file_name(f, flist, fbuf, &st,
FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags,
- NO_FILTERS);
+ NO_FILTERS, FIXME_path_depth(fbuf));
if (!file)
continue;
if (inc_recurse) {
send_dir_depth = 0;
change_local_filter_dir(fbuf, len, send_dir_depth);
}
- send_directory(f, flist, fbuf, len, flags);
+ send_directory(f, flist, fbuf, len, flags, F_DIR_PHYS_DEPTH(file));
}
} else
send_if_directory(f, flist, file, fbuf, len, flags);
} else
- send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS);
+ send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS,
+ FIXME_path_depth(fbuf));
}
gettimeofday(&end_tv, NULL);
recurse = 0;
xfer_dirs = 1;
- send_directory(ignore_filter_rules ? -2 : -1, dirlist, dirname, dlen, FLAG_CONTENT_DIR);
+ send_directory(ignore_filter_rules ? -2 : -1, dirlist, dirname, dlen,
+ FLAG_CONTENT_DIR, PHYS_DEPTH_N_A);
xfer_dirs = save_xfer_dirs;
recurse = save_recurse;
if (INFO_GTE(PROGRESS, 1))
return 1;
}
+/* Computes the depth of the given path, excluding any basename. This
+ * assumes that the path does not walk through any symlinks.
+ *
+ * For use with unsafe_symlink. */
+int path_depth(const char *path)
+{
+ const char *name, *slash;
+ int depth = 0;
+
+ for (name = path; (slash = strchr(name, '/')) != 0; name = slash+1) {
+ /* ".." segment starts the count over. "." segment is ignored. */
+ if (strncmp(name, "./", 2) == 0)
+ ;
+ else if (strncmp(name, "../", 3) == 0)
+ depth = 0;
+ else
+ depth++;
+ while (slash[1] == '/') slash++; /* just in case path isn't clean */
+ }
+ if (strcmp(name, "..") == 0)
+ depth = 0;
+
+ return depth;
+}
+
/* Determine if a symlink points outside the current directory tree.
* This is considered "unsafe" because e.g. when mirroring somebody
* else's machine it might allow them to establish a symlink to
*
* "dest" is the target of the symlink in question.
*
- * "src" is the top source directory currently applicable at the level
- * of the referenced symlink. This is usually the symlink's full path
- * (including its name), as referenced from the root of the transfer. */
-int unsafe_symlink(const char *dest, const char *src)
+ * "depth" is the depth of the symlink inside the current directory
+ * tree. It can be computed with "path_depth". */
+int unsafe_symlink(const char *dest, int depth)
{
const char *name, *slash;
- int depth = 0;
/* all absolute and null symlinks are unsafe */
if (!dest || !*dest || *dest == '/')
return 1;
- /* find out what our safety margin is */
- for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
- /* ".." segment starts the count over. "." segment is ignored. */
- if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
- if (name[1] == '.')
- depth = 0;
- } else
- depth++;
- while (slash[1] == '/') slash++; /* just in case src isn't clean */
- }
- if (*name == '.' && name[1] == '.' && name[2] == '\0')
- depth = 0;
-
for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) {
- if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
- if (name[1] == '.') {
- /* if at any point we go outside the current directory
- then stop - it is unsafe */
- if (--depth < 0)
- return 1;
- }
+ if (strncmp(name, "./", 2) == 0)
+ ;
+ else if (strncmp(name, "../", 3) == 0) {
+ /* if at any point we go outside the current directory
+ then stop - it is unsafe */
+ if (--depth < 0)
+ return 1;
} else
+ /* XXX: We could be walking through another symlink! */
depth++;
while (slash[1] == '/') slash++;
}
- if (*name == '.' && name[1] == '.' && name[2] == '\0')
- depth--;
+ if (strcmp(name, "..") == 0) {
+ if (--depth < 0)
+ return 1;
+ }
- return depth < 0;
+ return 0;
}
/* Return the date and time as a string. Some callers tweak returned buf. */