+ 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 newlines into '?'s. This ensures that
+ * outputting it on a line of its own cannot generate an empty line. This
+ * function can handle only 2 names at a time!
+ **/
+const char *safe_fname(const char *fname)
+{
+ static char fbuf1[MAXPATHLEN], fbuf2[MAXPATHLEN];
+ static char *fbuf = fbuf2;
+ char *nl = strchr(fname, '\n');
+
+ if (!nl)
+ return fname;
+
+ fbuf = fbuf == fbuf1 ? fbuf2 : fbuf1;
+ strlcpy(fbuf, fname, MAXPATHLEN);
+ nl = fbuf + (nl - (char *)fname);
+ do {
+ *nl = '?';
+ } while ((nl = strchr(nl+1, '\n')) != NULL);
+
+ return fbuf;
+}
+
+/**
+ * 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;
+ p2 = "/";
+ }
+ if (module_id >= 0) {
+ m1 = " (in ";
+ m2 = lp_name(module_id);
+ m3 = ")";
+ if (*p1) {
+ if (!lp_use_chroot(module_id)) {
+ char *p = lp_path(module_id);
+ if (*p != '/' || p[1])
+ p1 += strlen(p);
+ }
+ if (!*p1)
+ p2++;
+ else
+ p1++;
+ }
+ else
+ fn++;
+ } 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_exclude_list.head
+ && check_exclude(&server_exclude_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;
+#if SUPPORT_LINKS
+ int statret = do_lstat(dir, &st);
+#else
+ int statret = do_stat(dir, &st);
+#endif
+ 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);
+#else
+ strlcpy(TimeBuf, asctime(tm), sizeof TimeBuf);
+#endif
+
+ if (TimeBuf[strlen(TimeBuf)-1] == '\n') {
+ TimeBuf[strlen(TimeBuf)-1] = 0;
+ }
+
+ return(TimeBuf);
+}
+
+
+/**
+ * Sleep for a specified number of milliseconds.
+ *
+ * Always returns TRUE. (In the future it might return FALSE if
+ * interrupted.)
+ **/
+int msleep(int t)
+{
+ int tdiff = 0;
+ struct timeval tval, t1, t2;
+
+ gettimeofday(&t1, NULL);
+
+ while (tdiff < t) {
+ tval.tv_sec = (t-tdiff)/1000;
+ tval.tv_usec = 1000*((t-tdiff)%1000);
+
+ errno = 0;
+ select(0,NULL,NULL, NULL, &tval);
+
+ gettimeofday(&t2, NULL);
+ tdiff = (t2.tv_sec - t1.tv_sec)*1000 +
+ (t2.tv_usec - t1.tv_usec)/1000;
+ }
+
+ return True;
+}
+
+
+/**
+ * Determine if two file modification times are equivalent (either
+ * exact or in the modification timestamp window established by
+ * --modify-window).
+ *
+ * @retval 0 if the times should be treated as the same
+ *
+ * @retval +1 if the first is later
+ *
+ * @retval -1 if the 2nd is later
+ **/
+int cmp_modtime(time_t file1, time_t file2)
+{
+ if (file2 > file1) {
+ if (file2 - file1 <= modify_window)
+ return 0;
+ return -1;
+ }
+ if (file1 - file2 <= modify_window)
+ return 0;
+ return 1;
+}
+
+
+#ifdef __INSURE__XX
+#include <dlfcn.h>
+
+/**
+ This routine is a trick to immediately catch errors when debugging
+ with insure. A xterm with a gdb is popped up when insure catches
+ a error. It is Linux specific.
+**/
+int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6)
+{
+ static int (*fn)();
+ int ret;
+ char *cmd;
+
+ asprintf(&cmd, "/usr/X11R6/bin/xterm -display :0 -T Panic -n Panic -e /bin/sh -c 'cat /tmp/ierrs.*.%d ; gdb /proc/%d/exe %d'",
+ getpid(), getpid(), getpid());
+
+ if (!fn) {
+ static void *h;
+ h = dlopen("/usr/local/parasoft/insure++lite/lib.linux2/libinsure.so", RTLD_LAZY);
+ fn = dlsym(h, "_Insure_trap_error");
+ }
+
+ ret = fn(a1, a2, a3, a4, a5, a6);
+
+ system(cmd);
+
+ free(cmd);
+
+ return ret;
+}
+#endif
+
+
+#define MALLOC_MAX 0x40000000
+
+void *_new_array(unsigned int size, unsigned long num)
+{
+ if (num >= MALLOC_MAX/size)
+ return NULL;
+ return malloc(size * num);
+}
+
+void *_realloc_array(void *ptr, unsigned int size, unsigned long num)
+{
+ if (num >= MALLOC_MAX/size)
+ return NULL;
+ /* No realloc should need this, but just in case... */
+ if (!ptr)
+ return malloc(size * num);
+ return realloc(ptr, size * num);