+ if (*dir == '/') {
+ if (len >= sizeof curr_dir) {
+ errno = ENAMETOOLONG;
+ return 0;
+ }
+ if (!set_path_only && chdir(dir))
+ return 0;
+ memcpy(curr_dir, dir, len + 1);
+ } else {
+ if (curr_dir_len + 1 + len >= sizeof curr_dir) {
+ errno = ENAMETOOLONG;
+ return 0;
+ }
+ curr_dir[curr_dir_len] = '/';
+ memcpy(curr_dir + curr_dir_len + 1, dir, len + 1);
+
+ if (!set_path_only && chdir(curr_dir)) {
+ curr_dir[curr_dir_len] = '\0';
+ return 0;
+ }
+ }
+
+ curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS);
+ if (sanitize_paths) {
+ if (module_dirlen > curr_dir_len)
+ module_dirlen = curr_dir_len;
+ curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
+ }
+
+ if (DEBUG_GTE(CHDIR, 1) && !set_path_only)
+ rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir);
+
+ return 1;
+}
+
+/* This will make a relative path absolute and clean it up via clean_fname().
+ * Returns the string, which might be newly allocated, or NULL on error. */
+char *normalize_path(char *path, BOOL force_newbuf, unsigned int *len_ptr)
+{
+ unsigned int len;
+
+ if (*path != '/') { /* Make path absolute. */
+ int len = strlen(path);
+ if (curr_dir_len + 1 + len >= sizeof curr_dir)
+ return NULL;
+ curr_dir[curr_dir_len] = '/';
+ memcpy(curr_dir + curr_dir_len + 1, path, len + 1);
+ if (!(path = strdup(curr_dir)))
+ out_of_memory("normalize_path");
+ curr_dir[curr_dir_len] = '\0';
+ } else if (force_newbuf) {
+ if (!(path = strdup(path)))
+ out_of_memory("normalize_path");
+ }
+
+ len = clean_fname(path, CFN_COLLAPSE_DOT_DOT_DIRS | CFN_DROP_TRAILING_DOT_DIR);
+
+ if (len_ptr)
+ *len_ptr = len;
+
+ return path;
+}
+
+/**
+ * Return a quoted string with the full pathname of the indicated filename.
+ * The string " (in MODNAME)" may also be appended. The returned pointer
+ * remains valid until the next time full_fname() is called.
+ **/
+char *full_fname(const char *fn)
+{
+ static char *result = NULL;
+ char *m1, *m2, *m3;
+ char *p1, *p2;
+
+ if (result)
+ free(result);
+
+ if (*fn == '/')
+ p1 = p2 = "";
+ else {
+ p1 = curr_dir + module_dirlen;
+ for (p2 = p1; *p2 == '/'; p2++) {}
+ if (*p2)
+ p2 = "/";
+ }
+ if (module_id >= 0) {
+ m1 = " (in ";
+ m2 = lp_name(module_id);
+ m3 = ")";
+ } else
+ m1 = m2 = m3 = "";
+
+ if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) <= 0)
+ out_of_memory("full_fname");
+
+ return result;
+}
+
+static char partial_fname[MAXPATHLEN];
+
+char *partial_dir_fname(const char *fname)
+{
+ char *t = partial_fname;
+ int sz = sizeof partial_fname;
+ const char *fn;
+
+ if ((fn = strrchr(fname, '/')) != NULL) {
+ fn++;
+ if (*partial_dir != '/') {
+ int len = fn - fname;
+ strncpy(t, fname, len); /* safe */
+ t += len;
+ sz -= len;
+ }
+ } else
+ fn = fname;
+ if ((int)pathjoin(t, sz, partial_dir, fn) >= sz)
+ return NULL;
+ if (daemon_filter_list.head) {
+ t = strrchr(partial_fname, '/');
+ *t = '\0';
+ if (check_filter(&daemon_filter_list, FLOG, partial_fname, 1) < 0)
+ return NULL;
+ *t = '/';
+ if (check_filter(&daemon_filter_list, FLOG, partial_fname, 0) < 0)
+ return NULL;
+ }
+
+ return partial_fname;
+}
+
+/* If no --partial-dir option was specified, we don't need to do anything
+ * (the partial-dir is essentially '.'), so just return success. */
+int handle_partial_dir(const char *fname, int create)
+{
+ char *fn, *dir;
+
+ if (fname != partial_fname)
+ return 1;
+ if (!create && *partial_dir == '/')
+ return 1;
+ if (!(fn = strrchr(partial_fname, '/')))
+ return 1;
+
+ *fn = '\0';
+ dir = partial_fname;
+ if (create) {
+ STRUCT_STAT st;
+ int statret = do_lstat(dir, &st);
+ if (statret == 0 && !S_ISDIR(st.st_mode)) {
+ if (do_unlink(dir) < 0) {
+ *fn = '/';
+ return 0;
+ }
+ statret = -1;
+ }
+ if (statret < 0 && do_mkdir(dir, 0700) < 0) {
+ *fn = '/';
+ return 0;
+ }
+ } else
+ do_rmdir(dir);
+ *fn = '/';
+
+ return 1;
+}
+
+/**
+ * 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
+ * /etc/passwd, and then read it through a web server.
+ *
+ * Null symlinks and absolute symlinks are always unsafe.
+ *
+ * Basically here we are concerned with symlinks whose target contains
+ * "..", because this might cause us to walk back up out of the
+ * transferred directory. We are not allowed to go back up and
+ * reenter.
+ *
+ * @param dest Target of the symlink in question.
+ *
+ * @param src Top source directory currently applicable. Basically this
+ * is the first parameter to rsync in a simple invocation, but it's
+ * modified by flist.c in slightly complex ways.
+ *
+ * @retval True if unsafe
+ * @retval False is unsafe
+ *
+ * @sa t_unsafe.c
+ **/
+int unsafe_symlink(const char *dest, const char *src)
+{
+ 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) {
+ if (strncmp(name, "../", 3) == 0) {
+ depth = 0;
+ } else if (strncmp(name, "./", 2) == 0) {
+ /* nothing */
+ } else {
+ depth++;
+ }
+ }
+ if (strcmp(name, "..") == 0)
+ depth = 0;
+
+ for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) {
+ 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 if (strncmp(name, "./", 2) == 0) {
+ /* nothing */