+ while (*s) {
+ if (isupper(*(unsigned char *)s))
+ *s = tolower(*(unsigned char *)s);
+ s++;
+ }
+}
+
+/* Join strings p1 & p2 into "dest" with a guaranteed '/' between them. (If
+ * p1 ends with a '/', no extra '/' is inserted.) Returns the length of both
+ * strings + 1 (if '/' was inserted), regardless of whether the null-terminated
+ * string fits into destsize. */
+size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2)
+{
+ size_t len = strlcpy(dest, p1, destsize);
+ if (len < destsize - 1) {
+ if (!len || dest[len-1] != '/')
+ dest[len++] = '/';
+ if (len < destsize - 1)
+ len += strlcpy(dest + len, p2, destsize - len);
+ else {
+ dest[len] = '\0';
+ len += strlen(p2);
+ }
+ }
+ else
+ len += strlen(p2) + 1; /* Assume we'd insert a '/'. */
+ return len;
+}
+
+/* Join any number of strings together, putting them in "dest". The return
+ * value is the length of all the strings, regardless of whether the null-
+ * terminated whole fits in destsize. Your list of string pointers must end
+ * with a NULL to indicate the end of the list. */
+size_t stringjoin(char *dest, size_t destsize, ...)
+{
+ va_list ap;
+ size_t len, ret = 0;
+ const char *src;
+
+ va_start(ap, destsize);
+ while (1) {
+ if (!(src = va_arg(ap, const char *)))
+ break;
+ len = strlen(src);
+ ret += len;
+ if (destsize > 1) {
+ if (len >= destsize)
+ len = destsize - 1;
+ memcpy(dest, src, len);
+ destsize -= len;
+ dest += len;
+ }
+ }
+ *dest = '\0';
+ va_end(ap);
+
+ return ret;
+}
+
+int count_dir_elements(const char *p)
+{
+ int cnt = 0, new_component = 1;
+ while (*p) {
+ if (*p++ == '/')
+ new_component = 1;
+ else if (new_component) {
+ new_component = 0;
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+/* Turns multiple adjacent slashes into a single slash, gets rid of "./"
+ * elements (but not a trailing dot dir), removes a trailing slash, and
+ * optionally collapses ".." elements (except for those at the start of the
+ * string). If the resulting name would be empty, change it into a ".". */
+unsigned int clean_fname(char *name, BOOL collapse_dot_dot)
+{
+ char *limit = name - 1, *t = name, *f = name;
+ int anchored;
+
+ if (!name)
+ return 0;
+
+ if ((anchored = *f == '/') != 0)
+ *t++ = *f++;
+ while (*f) {
+ /* discard extra slashes */
+ if (*f == '/') {
+ f++;
+ continue;
+ }
+ if (*f == '.') {
+ /* discard "." dirs (but NOT a trailing '.'!) */
+ if (f[1] == '/') {
+ f += 2;
+ continue;
+ }
+ /* collapse ".." dirs */
+ if (collapse_dot_dot
+ && f[1] == '.' && (f[2] == '/' || !f[2])) {
+ char *s = t - 1;
+ if (s == name && anchored) {
+ f += 2;
+ continue;
+ }
+ while (s > limit && *--s != '/') {}
+ if (s != t - 1 && (s < name || *s == '/')) {
+ t = s + 1;
+ f += 2;
+ continue;
+ }
+ limit = t + 2;
+ }
+ }
+ while (*f && (*t++ = *f++) != '/') {}
+ }
+
+ if (t > name+anchored && t[-1] == '/')
+ t--;
+ if (t == name)
+ *t++ = '.';
+ *t = '\0';
+
+ return t - name;
+}
+
+/* Make path appear as if a chroot had occurred. This handles a leading
+ * "/" (either removing it or expanding it) and any leading or embedded
+ * ".." components that attempt to escape past the module's top dir.
+ *
+ * If dest is NULL, a buffer is allocated to hold the result. It is legal
+ * to call with the dest and the path (p) pointing to the same buffer, but
+ * rootdir will be ignored to avoid expansion of the string.
+ *
+ * The rootdir string contains a value to use in place of a leading slash.
+ * Specify NULL to get the default of lp_path(module_id).
+ *
+ * If depth is >= 0, it is a count of how many '..'s to allow at the start
+ * of the path. Use -1 to allow unlimited depth.
+ *
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences:
+ *
+ * Turns multiple adjacent slashes into a single slash, gets rid of "." dir
+ * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and
+ * ALWAYS collapses ".." elements (except for those at the start of the
+ * string up to "depth" deep). If the resulting name would be empty,
+ * change it into a ".". */
+char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth)
+{
+ char *start, *sanp;
+ int rlen = 0;
+
+ if (dest != p) {
+ int plen = strlen(p);
+ if (*p == '/') {
+ if (!rootdir)
+ rootdir = lp_path(module_id);
+ rlen = strlen(rootdir);
+ depth = 0;
+ p++;
+ }
+ if (dest) {
+ if (rlen + plen + 1 >= MAXPATHLEN)
+ return NULL;
+ } else if (!(dest = new_array(char, rlen + plen + 1)))
+ out_of_memory("sanitize_path");
+ if (rlen) {
+ memcpy(dest, rootdir, rlen);
+ if (rlen > 1)
+ dest[rlen++] = '/';
+ }
+ }
+
+ start = sanp = dest + rlen;
+ while (*p != '\0') {
+ /* discard leading or extra slashes */
+ if (*p == '/') {
+ p++;
+ continue;
+ }
+ /* this loop iterates once per filename component in p.
+ * both p (and sanp if the original had a slash) should
+ * always be left pointing after a slash
+ */
+ if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
+ /* skip "." component */
+ p++;
+ continue;
+ }
+ if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
+ /* ".." component followed by slash or end */
+ if (depth <= 0 || sanp != start) {
+ p += 2;
+ if (sanp != start) {
+ /* back up sanp one level */
+ --sanp; /* now pointing at slash */
+ while (sanp > start && sanp[-1] != '/') {
+ /* skip back up to slash */
+ sanp--;
+ }
+ }
+ continue;
+ }
+ /* allow depth levels of .. at the beginning */
+ depth--;
+ /* move the virtual beginning to leave the .. alone */
+ start = sanp + 3;
+ }
+ /* copy one component through next slash */
+ while (*p && (*sanp++ = *p++) != '/') {}
+ }
+ if (sanp == dest) {
+ /* ended up with nothing, so put in "." component */
+ *sanp++ = '.';
+ }
+ *sanp = '\0';
+
+ return dest;
+}
+
+char curr_dir[MAXPATHLEN];
+unsigned int curr_dir_len;
+
+/**
+ * Like chdir(), but it keeps track of the current directory (in the
+ * global "curr_dir"), and ensures that the path size doesn't overflow.
+ * Also cleans the path using the clean_fname() function.
+ **/
+int push_dir(char *dir)
+{
+ static int initialised;
+ unsigned int len;
+
+ if (!initialised) {
+ initialised = 1;
+ getcwd(curr_dir, sizeof curr_dir - 1);
+ curr_dir_len = strlen(curr_dir);
+ }
+
+ if (!dir) /* this call was probably just to initialize */
+ return 0;
+
+ len = strlen(dir);
+ if (len == 1 && *dir == '.')
+ return 1;
+
+ if ((*dir == '/' ? len : curr_dir_len + 1 + len) >= sizeof curr_dir)
+ return 0;
+
+ if (chdir(dir))
+ return 0;
+
+ if (*dir == '/') {
+ memcpy(curr_dir, dir, len + 1);
+ curr_dir_len = len;
+ } else {
+ curr_dir[curr_dir_len++] = '/';
+ memcpy(curr_dir + curr_dir_len, dir, len + 1);
+ curr_dir_len += len;
+ }
+
+ curr_dir_len = clean_fname(curr_dir, 1);
+
+ return 1;
+}
+
+/**
+ * Reverse a push_dir() call. You must pass in an absolute path
+ * that was copied from a prior value of "curr_dir".
+ **/
+int pop_dir(char *dir)
+{
+ if (chdir(dir))
+ return 0;
+
+ curr_dir_len = strlcpy(curr_dir, dir, sizeof curr_dir);
+ if (curr_dir_len >= sizeof curr_dir)
+ curr_dir_len = sizeof curr_dir - 1;
+
+ return 1;
+}
+
+/* Return the filename, turning any non-printable characters into escaped
+ * characters (e.g. \n -> \012, \ -> \\). This ensures that outputting it
+ * cannot generate an empty line nor corrupt the screen. This function can
+ * return only MAX_SAFE_NAMES values at a time! The returned value can be
+ * longer than MAXPATHLEN (because we may be trying to output an error about
+ * a too-long filename)! */
+char *safe_fname(const char *fname)
+{
+#define MAX_SAFE_NAMES 4
+ static char fbuf[MAX_SAFE_NAMES][MAXPATHLEN*2];
+ static int ndx = 0;
+ int limit = sizeof fbuf / MAX_SAFE_NAMES - 1;
+ char *t;
+
+ ndx = (ndx + 1) % MAX_SAFE_NAMES;
+ for (t = fbuf[ndx]; *fname && limit; fname++) {
+ if (*fname == '\\') {
+ if ((limit -= 2) < 0)
+ break;
+ *t++ = '\\';
+ *t++ = '\\';
+ } else if (!isprint(*(uchar*)fname)) {
+ if ((limit -= 4) < 0)
+ break;
+ sprintf(t, "\\%03o", *(uchar*)fname);
+ t += 4;
+ } else {
+ limit--;
+ *t++ = *fname;
+ }
+ }
+ *t = '\0';
+
+ return fbuf[ndx];
+}
+
+/**
+ * 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);
+
+ fn = safe_fname(fn);
+ if (*fn == '/')
+ p1 = p2 = "";
+ else {
+ p1 = curr_dir;
+ for (p2 = p1; *p2 == '/'; p2++) {}
+ if (*p2)
+ p2 = "/";
+ }
+ if (module_id >= 0) {
+ m1 = " (in ";
+ m2 = lp_name(module_id);
+ m3 = ")";
+ if (p1 == curr_dir) {
+ if (!lp_use_chroot(module_id)) {
+ char *p = lp_path(module_id);
+ if (*p != '/' || p[1])
+ p1 += strlen(p);
+ }
+ }
+ } else
+ m1 = m2 = m3 = "";
+
+ asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3);
+
+ 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 (server_filter_list.head) {
+ static int len;
+ if (!len)
+ len = strlen(partial_dir);
+ t[len] = '\0';
+ if (check_filter(&server_filter_list, partial_fname, 1) < 0)
+ return NULL;
+ t[len] = '/';
+ if (check_filter(&server_filter_list, 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)
+ return 0;
+ statret = -1;
+ }
+ if (statret < 0 && do_mkdir(dir, 0700) < 0)
+ return 0;
+ } else
+ do_rmdir(dir);
+ *fn = '/';
+
+ return 1;
+}
+
+/** We need to supply our own strcmp function for file list comparisons
+ to ensure that signed/unsigned usage is consistent between machines. */
+int u_strcmp(const char *cs1, const char *cs2)
+{
+ const uchar *s1 = (const uchar *)cs1;
+ const uchar *s2 = (const uchar *)cs2;
+
+ while (*s1 && *s2 && (*s1 == *s2)) {
+ s1++; s2++;
+ }
+
+ return (int)*s1 - (int)*s2;
+}
+
+
+
+/**
+ * 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 */
+ } else {
+ depth++;
+ }
+ }
+ if (strcmp(name, "..") == 0)
+ depth--;
+
+ return (depth < 0);
+}
+
+
+/**
+ * Return the date and time as a string
+ **/
+char *timestring(time_t t)
+{
+ static char TimeBuf[200];
+ struct tm *tm = localtime(&t);
+
+#ifdef HAVE_STRFTIME
+ strftime(TimeBuf, sizeof TimeBuf - 1, "%Y/%m/%d %H:%M:%S", tm);