Make sure secrets file is not other-accessible, and owned by root if the
[rsync/rsync.git] / util.c
diff --git a/util.c b/util.c
index 15ab69d..7a4fbb8 100644 (file)
--- a/util.c
+++ b/util.c
@@ -61,14 +61,14 @@ int piped_child(char **command,int *f_in,int *f_out)
   if (pipe(to_child_pipe) < 0 ||
       pipe(from_child_pipe) < 0) {
     rprintf(FERROR,"pipe: %s\n",strerror(errno));
-    exit_cleanup(1);
+    exit_cleanup(RERR_IPC);
   }
 
 
   pid = do_fork();
   if (pid < 0) {
     rprintf(FERROR,"fork: %s\n",strerror(errno));
-    exit_cleanup(1);
+    exit_cleanup(RERR_IPC);
   }
 
   if (pid == 0)
@@ -79,7 +79,7 @@ int piped_child(char **command,int *f_in,int *f_out)
          close(from_child_pipe[0]) < 0 ||
          dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
        rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
-       exit_cleanup(1);
+       exit_cleanup(RERR_IPC);
       }
       if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
       if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
@@ -87,17 +87,20 @@ int piped_child(char **command,int *f_in,int *f_out)
       execvp(command[0], command);
       rprintf(FERROR,"Failed to exec %s : %s\n",
              command[0],strerror(errno));
-      exit_cleanup(1);
+      exit_cleanup(RERR_IPC);
     }
 
   if (close(from_child_pipe[1]) < 0 ||
       close(to_child_pipe[0]) < 0) {
     rprintf(FERROR,"Failed to close : %s\n",strerror(errno));   
-    exit_cleanup(1);
+    exit_cleanup(RERR_IPC);
   }
 
   *f_in = from_child_pipe[0];
   *f_out = to_child_pipe[1];
+
+  set_nonblocking(*f_in);
+  set_nonblocking(*f_out);
   
   return pid;
 }
@@ -111,14 +114,14 @@ int local_child(int argc, char **argv,int *f_in,int *f_out)
        if (pipe(to_child_pipe) < 0 ||
            pipe(from_child_pipe) < 0) {
                rprintf(FERROR,"pipe: %s\n",strerror(errno));
-               exit_cleanup(1);
+               exit_cleanup(RERR_IPC);
        }
 
 
        pid = do_fork();
        if (pid < 0) {
                rprintf(FERROR,"fork: %s\n",strerror(errno));
-               exit_cleanup(1);
+               exit_cleanup(RERR_IPC);
        }
 
        if (pid == 0) {
@@ -133,7 +136,7 @@ int local_child(int argc, char **argv,int *f_in,int *f_out)
                    close(from_child_pipe[0]) < 0 ||
                    dup2(from_child_pipe[1], STDOUT_FILENO) < 0) {
                        rprintf(FERROR,"Failed to dup/close : %s\n",strerror(errno));
-                       exit_cleanup(1);
+                       exit_cleanup(RERR_IPC);
                }
                if (to_child_pipe[0] != STDIN_FILENO) close(to_child_pipe[0]);
                if (from_child_pipe[1] != STDOUT_FILENO) close(from_child_pipe[1]);
@@ -143,7 +146,7 @@ int local_child(int argc, char **argv,int *f_in,int *f_out)
        if (close(from_child_pipe[1]) < 0 ||
            close(to_child_pipe[0]) < 0) {
                rprintf(FERROR,"Failed to close : %s\n",strerror(errno));   
-               exit_cleanup(1);
+               exit_cleanup(RERR_IPC);
        }
 
        *f_in = from_child_pipe[0];
@@ -157,13 +160,13 @@ int local_child(int argc, char **argv,int *f_in,int *f_out)
 void out_of_memory(char *str)
 {
   rprintf(FERROR,"ERROR: out of memory in %s\n",str);
-  exit_cleanup(1);
+  exit_cleanup(RERR_MALLOC);
 }
 
 void overflow(char *str)
 {
   rprintf(FERROR,"ERROR: buffer overflow in %s\n",str);
-  exit_cleanup(1);
+  exit_cleanup(RERR_MALLOC);
 }
 
 
@@ -250,7 +253,7 @@ static int full_write(int desc, char *ptr, int len)
    for an error.  
 
    derived from GNU C's cccp.c. */
-int safe_read(int desc, char *ptr, int len)
+static int safe_read(int desc, char *ptr, int len)
 {
        int n_chars;
  
@@ -291,7 +294,7 @@ int copy_file(char *source, char *dest, mode_t mode)
        }
 
        ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
-       if (ofd < 0) {
+       if (ofd == -1) {
                rprintf(FERROR,"open %s: %s\n",
                        dest,strerror(errno));
                close(ifd);
@@ -355,31 +358,6 @@ void kill_all(int sig)
        }
 }
 
-/* like strncpy but does not 0 fill the buffer and always null 
-   terminates (thus it can use maxlen+1 space in d) */
-void strlcpy(char *d, char *s, int maxlen)
-{
-       int len = strlen(s);
-       if (len > maxlen) len = maxlen;
-       memcpy(d, s, len);
-       d[len] = 0;
-}
-
-/* like strncat but does not 0 fill the buffer and always null 
-   terminates (thus it can use maxlen+1 space in d) */
-void strlcat(char *d, char *s, int maxlen)
-{
-       int len1 = strlen(d);
-       int len2 = strlen(s);
-       if (len1+len2 > maxlen) {
-               len2 = maxlen-len1;
-       }
-       if (len2 > 0) {
-               memcpy(d+len1, s, len2);
-               d[len1+len2] = 0;
-       }
-}
-
 /* turn a user name into a uid */
 int name_to_uid(char *name, uid_t *uid)
 {
@@ -407,14 +385,6 @@ int name_to_gid(char *name, gid_t *gid)
 }
 
 
-/****************************************************************************
-check if a process exists. 
-****************************************************************************/
-int process_exists(int pid)
-{
-       return(kill(pid,0) == 0 || errno != ESRCH);
-}
-
 /* lock a byte range in a open file */
 int lock_range(int fd, int offset, int len)
 {
@@ -430,9 +400,9 @@ int lock_range(int fd, int offset, int len)
 }
 
 
-static void glob_expand_one(char *s, char **argv, int *argc, int maxargs)
+static void glob_expand_one(char *s, char **argv, int *argc, int maxargs, int sanitize_paths)
 {
-#ifndef HAVE_GLOB
+#if !(defined(HAVE_GLOB) && defined(HAVE_GLOB_H))
        if (!*s) s = ".";
        argv[*argc] = strdup(s);
        (*argc)++;
@@ -443,7 +413,7 @@ static void glob_expand_one(char *s, char **argv, int *argc, int maxargs)
 
        if (!*s) s = ".";
 
-       argv[*argc] = strdup(s);
+       argv[*argc] = sanitize_paths ? sanitize_path(s) : strdup(s);
 
        memset(&globbuf, 0, sizeof(globbuf));
        glob(argv[*argc], 0, NULL, &globbuf);
@@ -462,7 +432,7 @@ static void glob_expand_one(char *s, char **argv, int *argc, int maxargs)
 #endif
 }
 
-void glob_expand(char *base1, char **argv, int *argc, int maxargs)
+void glob_expand(char *base1, char **argv, int *argc, int maxargs, int sanitize_paths)
 {
        char *s = argv[*argc];
        char *p, *q;
@@ -486,11 +456,11 @@ void glob_expand(char *base1, char **argv, int *argc, int maxargs)
        while ((p = strstr(q,base)) && ((*argc) < maxargs)) {
                /* split it at this point */
                *p = 0;
-               glob_expand_one(q, argv, argc, maxargs);
+               glob_expand_one(q, argv, argc, maxargs, sanitize_paths);
                q = p+strlen(base);
        }
 
-       if (*q && (*argc < maxargs)) glob_expand_one(q, argv, argc, maxargs);
+       if (*q && (*argc < maxargs)) glob_expand_one(q, argv, argc, maxargs, sanitize_paths);
 
        free(s);
        free(base);
@@ -507,55 +477,17 @@ void strlower(char *s)
        }
 }
 
-/* this is like vsnprintf but the 'n' limit does not include
-   the terminating null. So if you have a 1024 byte buffer then
-   pass 1023 for n */
+/* this is like vsnprintf but it always null terminates, so you
+   can fit at most n-1 chars in */
 int vslprintf(char *str, int n, const char *format, va_list ap)
 {
-#ifdef HAVE_VSNPRINTF
        int ret = vsnprintf(str, n, format, ap);
-       if (ret > n || ret < 0) {
-               str[n] = 0;
+       if (ret >= n || ret < 0) {
+               str[n-1] = 0;
                return -1;
        }
        str[ret] = 0;
        return ret;
-#else
-       static char *buf;
-       static int len=MAXPATHLEN*8;
-       int ret;
-
-       /* this code is NOT a proper vsnprintf() implementation. It
-          relies on the fact that all calls to slprintf() in rsync
-          pass strings which have already been checked to be less
-          than MAXPATHLEN in length and never more than 2 strings are
-          concatenated. This means the above buffer is absolutely
-          ample and can never be overflowed.
-
-          In the future we would like to replace this with a proper
-          vsnprintf() implementation but right now we need a solution
-          that is secure and portable. This is it.  */
-
-       if (!buf) {
-               buf = malloc(len);
-               if (!buf) {
-                       /* can't call debug or we would recurse */
-                       exit_cleanup(1);
-               }
-       }
-
-       vsprintf(buf, format, ap);
-       ret = strlen(buf);
-       if (ret > n) {
-               /* yikes! */
-               exit_cleanup(1);
-       }
-       buf[ret] = 0;
-       
-       memcpy(str, buf, ret+1);
-
-       return ret;
-#endif
 }
 
 
@@ -621,6 +553,75 @@ 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
+ *    3. delete any other "<dir>/.." (recursively)
+ * While we're at it, remove double slashes and "." components like
+ *   clean_fname does(), but DON'T remove a trailing slash because that
+ *   is sometimes significant on command line arguments.
+ * Return a malloc'ed copy.
+ * Contributed by Dave Dykstra <dwd@bell-labs.com>
+ */
+
+char *sanitize_path(char *p)
+{
+       char *copy, *copyp;
+
+       copy = (char *) malloc(strlen(p)+1);
+       copyp = copy;
+       while (*p == '/') {
+               /* remove leading slashes */
+               p++;
+       }
+       while (*p != '\0') {
+               /* this loop iterates once per filename component in p.
+                * both p (and copyp if the original had a slash) should
+                * always be left pointing after a slash
+                */
+               if ((*p == '.') && ((*(p+1) == '/') || (*(p+1) == '\0'))) {
+                       /* skip "." component */
+                       while (*++p == '/') {
+                               /* skip following slashes */
+                               ;
+                       }
+               } else if ((*p == '.') && (*(p+1) == '.') &&
+                           ((*(p+2) == '/') || (*(p+2) == '\0'))) {
+                       /* skip ".." component followed by slash or end */
+                       p += 2;
+                       if (*p == '/')
+                               p++;
+                       if (copyp != copy) {
+                               /* back up the copy one level */
+                               --copyp; /* now pointing at slash */
+                               while ((copyp > copy) && (*(copyp - 1) != '/')) {
+                                       /* skip back up to slash */
+                                       copyp--;
+                               }
+                       }
+               } else {
+                       while (1) {
+                               /* copy one component through next slash */
+                               *copyp++ = *p++;
+                               if ((*p == '\0') || (*(p-1) == '/')) {
+                                       while (*p == '/') {
+                                               /* skip multiple slashes */
+                                               p++;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+       }
+       if (copyp == copy) {
+               /* ended up with nothing, so put in "." component */
+               *copyp++ = '.';
+       }
+       *copyp = '\0';
+       return(copy);
+}
+
 
 static char curr_dir[MAXPATHLEN];
 
@@ -643,10 +644,10 @@ char *push_dir(char *dir, int save)
        }
 
        if (*dir == '/') {
-               strlcpy(curr_dir, dir, sizeof(curr_dir)-1);
+               strlcpy(curr_dir, dir, sizeof(curr_dir));
        } else {
-               strlcat(curr_dir,"/", sizeof(curr_dir)-1);
-               strlcat(curr_dir,dir, sizeof(curr_dir)-1);
+               strlcat(curr_dir,"/", sizeof(curr_dir));
+               strlcat(curr_dir,dir, sizeof(curr_dir));
        }
 
        clean_fname(curr_dir);
@@ -665,7 +666,7 @@ int pop_dir(char *dir)
                return ret;
        }
 
-       strlcpy(curr_dir, dir, sizeof(curr_dir)-1);
+       strlcpy(curr_dir, dir, sizeof(curr_dir));
 
        free(dir);
 
@@ -676,8 +677,8 @@ int pop_dir(char *dir)
    to ensure that signed/unsigned usage is consistent between machines. */
 int u_strcmp(const char *cs1, const char *cs2)
 {
-       const uchar *s1 = (uchar *)cs1;
-       const uchar *s2 = (uchar *)cs2;
+       const uchar *s1 = (const uchar *)cs1;
+       const uchar *s2 = (const uchar *)cs2;
 
        while (*s1 && *s2 && (*s1 == *s2)) {
                s1++; s2++;
@@ -710,3 +711,73 @@ void show_progress(OFF_T ofs, OFF_T size)
                }
        }
 }
+
+/* determine if a symlink points outside the current directory tree */
+int unsafe_symlink(char *dest, char *src)
+{
+       char *tok;
+       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");
+
+       /* find out what our safety margin is */
+       for (tok=strtok(src,"/"); tok; tok=strtok(NULL,"/")) {
+               if (strcmp(tok,"..") == 0) {
+                       depth=0;
+               } else if (strcmp(tok,".") == 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) {
+                       /* nothing */
+               } else {
+                       depth++;
+               }
+               /* if at any point we go outside the current directory then
+                  stop - it is unsafe */
+               if (depth < 0) break;
+       }
+
+       free(dest);
+       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 %T",tm);
+#else
+       strlcpy(TimeBuf, asctime(tm), sizeof(TimeBuf));
+#endif
+
+       if (TimeBuf[strlen(TimeBuf)-1] == '\n') {
+               TimeBuf[strlen(TimeBuf)-1] = 0;
+       }
+
+       return(TimeBuf);
+}
+