X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/0ecfbf27c3b9e01d498201e90e7d0a89a9d0a47c..368ad70e050a318115fefacf558d033f49274704:/util.c diff --git a/util.c b/util.c index 00670db4..fe81f2ad 100644 --- a/util.c +++ b/util.c @@ -197,12 +197,10 @@ static int full_write(int desc, char *ptr, size_t len) total_written = 0; while (len > 0) { - int written = write (desc, ptr, len); + int written = write(desc, ptr, len); if (written < 0) { -#ifdef EINTR if (errno == EINTR) continue; -#endif return written; } total_written += written; @@ -231,13 +229,9 @@ static int safe_read(int desc, char *ptr, size_t len) if (len == 0) return len; -#ifdef EINTR do { n_chars = read(desc, ptr, len); } while (n_chars < 0 && errno == EINTR); -#else - n_chars = read(desc, ptr, len); -#endif return n_chars; } @@ -301,16 +295,15 @@ int copy_file(char *source, char *dest, mode_t mode) #define MAX_RENAMES 1000 /** - * - Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so - rename to /.rsyncNNN instead. - - Note that successive rsync runs will shuffle the filenames around a - bit as long as the file is still busy; this is because this function - does not know if the unlink call is due to a new file coming in, or - --delete trying to remove old .rsyncNNN files, hence it renames it - each time. -*/ + * Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so + * rename to /.rsyncNNN instead. + * + * Note that successive rsync runs will shuffle the filenames around a + * bit as long as the file is still busy; this is because this function + * does not know if the unlink call is due to a new file coming in, or + * --delete trying to remove old .rsyncNNN files, hence it renames it + * each time. + **/ int robust_unlink(char *fname) { #ifndef ETXTBSY @@ -346,9 +339,10 @@ int robust_unlink(char *fname) counter = 1; } while (((rc = access(path, 0)) == 0) && (counter != start)); - if (verbose > 0) + if (verbose > 0) { rprintf(FINFO,"renaming %s to %s because of text busy\n", - fname, path); + fname, path); + } /* maybe we should return rename()'s exit status? Nah. */ if (do_rename(fname, path) != 0) { @@ -459,14 +453,31 @@ int lock_range(int fd, int offset, int len) return fcntl(fd,F_SETLK,&lock) == 0; } +static int exclude_server_path(char *arg) +{ + char *s; + extern struct exclude_struct **server_exclude_list; + + if (server_exclude_list) { + for (s = arg; (s = strchr(s, '/')) != NULL; ) { + *s = '\0'; + if (check_exclude(server_exclude_list, arg, 1)) { + /* We must leave arg truncated! */ + return 1; + } + *s++ = '/'; + } + } + return 0; +} static void glob_expand_one(char *s, char **argv, int *argc, int maxargs) { #if !(defined(HAVE_GLOB) && defined(HAVE_GLOB_H)) if (!*s) s = "."; - argv[*argc] = strdup(s); + s = argv[*argc] = strdup(s); + exclude_server_path(s); (*argc)++; - return; #else extern int sanitize_paths; glob_t globbuf; @@ -474,20 +485,21 @@ static void glob_expand_one(char *s, char **argv, int *argc, int maxargs) if (!*s) s = "."; - argv[*argc] = strdup(s); + s = argv[*argc] = strdup(s); if (sanitize_paths) { - sanitize_path(argv[*argc], NULL); + sanitize_path(s, NULL); } memset(&globbuf, 0, sizeof(globbuf)); - glob(argv[*argc], 0, NULL, &globbuf); + if (!exclude_server_path(s)) + glob(s, 0, NULL, &globbuf); if (globbuf.gl_pathc == 0) { (*argc)++; globfree(&globbuf); return; } for (i=0; i<(maxargs - (*argc)) && i < (int) globbuf.gl_pathc;i++) { - if (i == 0) free(argv[*argc]); + if (i == 0) free(s); argv[(*argc) + i] = strdup(globbuf.gl_pathv[i]); if (!argv[(*argc) + i]) out_of_memory("glob_expand"); } @@ -496,29 +508,31 @@ static void glob_expand_one(char *s, char **argv, int *argc, int maxargs) #endif } +/* This routine is only used in daemon mode. */ void glob_expand(char *base1, char **argv, int *argc, int maxargs) { char *s = argv[*argc]; char *p, *q; char *base = base1; + int base_len = strlen(base); if (!s || !*s) return; - if (strncmp(s, base, strlen(base)) == 0) { - s += strlen(base); - } + if (strncmp(s, base, base_len) == 0) + s += base_len; s = strdup(s); if (!s) out_of_memory("glob_expand"); if (asprintf(&base," %s/", base1) <= 0) out_of_memory("glob_expand"); + base_len++; q = s; while ((p = strstr(q,base)) && ((*argc) < maxargs)) { /* split it at this point */ *p = 0; glob_expand_one(q, argv, argc, maxargs); - q = p+strlen(base); + q = p + base_len; } if (*q && (*argc < maxargs)) glob_expand_one(q, argv, argc, maxargs); @@ -533,17 +547,63 @@ void glob_expand(char *base1, char **argv, int *argc, int maxargs) void strlower(char *s) { while (*s) { - if (isupper(*s)) *s = tolower(*s); + if (isupper(* (unsigned char *) s)) + *s = tolower(* (unsigned char *) s); s++; } } -void *Realloc(void *p, int size) +/* 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 whole thing + * fits into destsize (including the terminating '\0'). */ +size_t pathjoin(char *dest, size_t destsize, const char *p1, const char *p2) { - if (!p) return (void *)malloc(size); - return (void *)realloc(p, size); + 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 they fit in + * destsize (including the terminating '\0'). Your list of string pointers + * should 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; +} void clean_fname(char *name) { @@ -590,17 +650,19 @@ void clean_fname(char *name) /** * Make path appear as if a chroot had occurred: * - * 1. remove leading "/" (or replace with "." if at end) - * 2. remove leading ".." components (except those allowed by "reldir") - * 3. delete any other "/.." (recursively) + * @li 1. remove leading "/" (or replace with "." if at end) + * + * @li 2. remove leading ".." components (except those allowed by @p reldir) + * + * @li 3. delete any other "/.." (recursively) * * Can only shrink paths, so sanitizes in place. * * While we're at it, remove double slashes and "." components like - * clean_fname does(), but DON'T remove a trailing slash because that + * clean_fname() does, but DON'T remove a trailing slash because that * is sometimes significant on command line arguments. * - * If "reldir" is non-null, it is a sanitized directory that the path will be + * If @p reldir is non-null, it is a sanitized directory that the path will be * relative to, so allow as many ".." at the beginning of the path as * there are components in reldir. This is used for symbolic link targets. * If reldir is non-null and the path began with "/", to be completely like @@ -646,7 +708,7 @@ void sanitize_path(char *p, char *reldir) } allowdotdot = 0; if ((*p == '.') && (*(p+1) == '.') && - ((*(p+2) == '/') || (*(p+2) == '\0'))) { + ((*(p+2) == '/') || (*(p+2) == '\0'))) { /* ".." component followed by slash or end */ if ((depth > 0) && (sanp == start)) { /* allow depth levels of .. at the beginning */ @@ -697,10 +759,12 @@ void sanitize_path(char *p, char *reldir) } -static char curr_dir[MAXPATHLEN]; +char curr_dir[MAXPATHLEN]; -/** like chdir() but can be reversed with pop_dir() if save is set. It - is also much faster as it remembers where we have been */ +/** + * Like chdir() but can be reversed with pop_dir() if @p save is set. + * It is also much faster as it remembers where we have been. + **/ char *push_dir(char *dir, int save) { char *ret = curr_dir; @@ -721,7 +785,7 @@ char *push_dir(char *dir, int save) if (*dir == '/') { strlcpy(curr_dir, dir, sizeof(curr_dir)); - } else { + } else if (dir[0] != '.' || dir[1] != '\0') { strlcat(curr_dir,"/", sizeof(curr_dir)); strlcat(curr_dir,dir, sizeof(curr_dir)); } @@ -731,7 +795,7 @@ char *push_dir(char *dir, int save) return ret; } -/** Reverse a push_dir call */ +/** Reverse a push_dir() call */ int pop_dir(char *dir) { int ret; @@ -749,6 +813,52 @@ int pop_dir(char *dir) return 0; } +/** + * 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(char *fn) +{ + extern int module_id; + static char *result = NULL; + char *m1, *m2, *m3; + char *p1, *p2; + + if (result) + free(result); + + 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; +} + /** 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) @@ -771,6 +881,13 @@ int u_strcmp(const char *cs1, const char *cs2) * 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 @@ -779,57 +896,52 @@ int u_strcmp(const char *cs1, const char *cs2) * * @retval True if unsafe * @retval False is unsafe + * + * @sa t_unsafe.c **/ -int unsafe_symlink(char *dest, char *src) +int unsafe_symlink(const char *dest, const char *src) { - char *tok; + const char *name, *slash; int depth = 0; /* all absolute and null symlinks are unsafe */ - if (!dest || !(*dest) || (*dest == '/')) return 1; - - src = strdup(src); - if (!src) out_of_memory("unsafe_symlink"); + if (!dest || !*dest || *dest == '/') return 1; /* find out what our safety margin is */ - for (tok=strtok(src,"/"); tok; tok=strtok(NULL,"/")) { - if (strcmp(tok,"..") == 0) { + for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) { + if (strncmp(name, "../", 3) == 0) { depth=0; - } else if (strcmp(tok,".") == 0) { + } else if (strncmp(name, "./", 2) == 0) { /* nothing */ } else { depth++; } } - free(src); - - /* drop by one to account for the filename portion */ - depth--; - - dest = strdup(dest); - if (!dest) out_of_memory("unsafe_symlink"); - - for (tok=strtok(dest,"/"); tok; tok=strtok(NULL,"/")) { - if (strcmp(tok,"..") == 0) { - depth--; - } else if (strcmp(tok,".") == 0) { + 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 at any point we go outside the current directory then - stop - it is unsafe */ - if (depth < 0) break; } + if (strcmp(name, "..") == 0) + depth--; - free(dest); return (depth < 0); } /** - Return the date and time as a string -*/ + * Return the date and time as a string + **/ char *timestring(time_t t) { static char TimeBuf[200]; @@ -935,3 +1047,23 @@ int _Insure_trap_error(int a1, int a2, int a3, int a4, int a5, int a6) 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); +}