]> andersk Git - openssh.git/blobdiff - sftp-client.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / sftp-client.c
index 2565a704d410debb1232b75b70231a9770a97ead..6124c0f408cc2ed731786a28b3bdcb47b069a4b1 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.85 2008/06/12 20:47:04 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.90 2009/10/11 10:41:26 dtucker Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -36,6 +36,7 @@
 #endif
 #include <sys/uio.h>
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <signal.h>
@@ -61,6 +62,9 @@ extern int showprogress;
 /* Minimum amount of data to read at a time */
 #define MIN_READ_SIZE  512
 
+/* Maximum depth to descend in directory trees */
+#define MAX_DIR_DEPTH 64
+
 struct sftp_conn {
        int fd_in;
        int fd_out;
@@ -74,6 +78,10 @@ struct sftp_conn {
        u_int exts;
 };
 
+static char *
+get_handle(int fd, u_int expected_id, u_int *len, const char *errfmt, ...)
+    __attribute__((format(printf, 4, 5)));
+
 static void
 send_msg(int fd, Buffer *m)
 {
@@ -179,11 +187,18 @@ get_status(int fd, u_int expected_id)
 }
 
 static char *
-get_handle(int fd, u_int expected_id, u_int *len)
+get_handle(int fd, u_int expected_id, u_int *len, const char *errfmt, ...)
 {
        Buffer msg;
        u_int type, id;
-       char *handle;
+       char *handle, errmsg[256];
+       va_list args;
+       int status;
+
+       va_start(args, errfmt);
+       if (errfmt != NULL)
+               vsnprintf(errmsg, sizeof(errmsg), errfmt, args);
+       va_end(args);
 
        buffer_init(&msg);
        get_msg(fd, &msg);
@@ -191,16 +206,17 @@ get_handle(int fd, u_int expected_id, u_int *len)
        id = buffer_get_int(&msg);
 
        if (id != expected_id)
-               fatal("ID mismatch (%u != %u)", id, expected_id);
+               fatal("%s: ID mismatch (%u != %u)",
+                   errfmt == NULL ? __func__ : errmsg, id, expected_id);
        if (type == SSH2_FXP_STATUS) {
-               int status = buffer_get_int(&msg);
-
-               error("Couldn't get handle: %s", fx2txt(status));
+               status = buffer_get_int(&msg);
+               if (errfmt != NULL)
+                       error("%s: %s", errmsg, fx2txt(status));
                buffer_free(&msg);
                return(NULL);
        } else if (type != SSH2_FXP_HANDLE)
-               fatal("Expected SSH2_FXP_HANDLE(%u) packet, got %u",
-                   SSH2_FXP_HANDLE, type);
+               fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u",
+                   errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type);
 
        handle = buffer_get_string(&msg, len);
        buffer_free(&msg);
@@ -418,7 +434,8 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
 
        buffer_clear(&msg);
 
-       handle = get_handle(conn->fd_in, id, &handle_len);
+       handle = get_handle(conn->fd_in, id, &handle_len,
+           "remote readdir(\"%s\")", path);
        if (handle == NULL)
                return(-1);
 
@@ -484,6 +501,17 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
                        if (printflag)
                                printf("%s\n", longname);
 
+                       /*
+                        * Directory entries should never contain '/'
+                        * These can be used to attack recursive ops
+                        * (e.g. send '../../../../etc/passwd')
+                        */
+                       if (strchr(filename, '/') != NULL) {
+                               error("Server sent suspect path \"%s\" "
+                                   "during readdir of \"%s\"", filename, path);
+                               goto next;
+                       }
+
                        if (dir) {
                                *dir = xrealloc(*dir, ents + 2, sizeof(**dir));
                                (*dir)[ents] = xmalloc(sizeof(***dir));
@@ -492,7 +520,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
                                memcpy(&(*dir)[ents]->a, a, sizeof(*a));
                                (*dir)[++ents] = NULL;
                        }
-
+ next:
                        xfree(filename);
                        xfree(longname);
                }
@@ -547,7 +575,7 @@ do_rm(struct sftp_conn *conn, char *path)
 }
 
 int
-do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
+do_mkdir(struct sftp_conn *conn, char *path, Attrib *a, int printflag)
 {
        u_int status, id;
 
@@ -556,7 +584,7 @@ do_mkdir(struct sftp_conn *conn, char *path, Attrib *a)
            strlen(path), a);
 
        status = get_status(conn->fd_in, id);
-       if (status != SSH2_FX_OK)
+       if (status != SSH2_FX_OK && printflag)
                error("Couldn't create directory: %s", fx2txt(status));
 
        return(status);
@@ -895,9 +923,9 @@ send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
 
 int
 do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
-    int pflag)
+    Attrib *a, int pflag)
 {
-       Attrib junk, *a;
+       Attrib junk;
        Buffer msg;
        char *handle;
        int local_fd, status = 0, write_error;
@@ -916,11 +944,10 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
 
        TAILQ_INIT(&requests);
 
-       a = do_stat(conn, remote_path, 0);
-       if (a == NULL)
-               return(-1);
+       if (a == NULL && (a = do_stat(conn, remote_path, 0)) == NULL)
+               return -1;
 
-       /* XXX: should we preserve set[ug]id? */
+       /* Do not preserve set[ug]id here, as we do not preserve ownership */
        if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
                mode = a->perm & 0777;
        else
@@ -951,7 +978,8 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
        send_msg(conn->fd_out, &msg);
        debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, remote_path);
 
-       handle = get_handle(conn->fd_in, id, &handle_len);
+       handle = get_handle(conn->fd_in, id, &handle_len,
+           "remote open(\"%s\")", remote_path);
        if (handle == NULL) {
                buffer_free(&msg);
                return(-1);
@@ -1132,6 +1160,114 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
        return(status);
 }
 
+static int
+download_dir_internal(struct sftp_conn *conn, char *src, char *dst,
+    Attrib *dirattrib, int pflag, int printflag, int depth)
+{
+       int i, ret = 0;
+       SFTP_DIRENT **dir_entries;
+       char *filename, *new_src, *new_dst;
+       mode_t mode = 0777;
+
+       if (depth >= MAX_DIR_DEPTH) {
+               error("Maximum directory depth exceeded: %d levels", depth);
+               return -1;
+       }
+
+       if (dirattrib == NULL &&
+           (dirattrib = do_stat(conn, src, 1)) == NULL) {
+               error("Unable to stat remote directory \"%s\"", src);
+               return -1;
+       }
+       if (!S_ISDIR(dirattrib->perm)) {
+               error("\"%s\" is not a directory", src);
+               return -1;
+       }
+       if (printflag)
+               printf("Retrieving %s\n", src);
+
+       if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
+               mode = dirattrib->perm & 01777;
+       else {
+               debug("Server did not send permissions for "
+                   "directory \"%s\"", dst);
+       }
+
+       if (mkdir(dst, mode) == -1 && errno != EEXIST) {
+               error("mkdir %s: %s", dst, strerror(errno));
+               return -1;
+       }
+
+       if (do_readdir(conn, src, &dir_entries) == -1) {
+               error("%s: Failed to get directory contents", src);
+               return -1;
+       }
+
+       for (i = 0; dir_entries[i] != NULL && !interrupted; i++) {
+               filename = dir_entries[i]->filename;
+
+               new_dst = path_append(dst, filename);
+               new_src = path_append(src, filename);
+
+               if (S_ISDIR(dir_entries[i]->a.perm)) {
+                       if (strcmp(filename, ".") == 0 ||
+                           strcmp(filename, "..") == 0)
+                               continue;
+                       if (download_dir_internal(conn, new_src, new_dst,
+                           &(dir_entries[i]->a), pflag, printflag,
+                           depth + 1) == -1)
+                               ret = -1;
+               } else if (S_ISREG(dir_entries[i]->a.perm) ) {
+                       if (do_download(conn, new_src, new_dst,
+                           &(dir_entries[i]->a), pflag) == -1) {
+                               error("Download of file %s to %s failed",
+                                   new_src, new_dst);
+                               ret = -1;
+                       }
+               } else
+                       logit("%s: not a regular file\n", new_src);
+
+               xfree(new_dst);
+               xfree(new_src);
+       }
+
+       if (pflag) {
+               if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) {
+                       struct timeval tv[2];
+                       tv[0].tv_sec = dirattrib->atime;
+                       tv[1].tv_sec = dirattrib->mtime;
+                       tv[0].tv_usec = tv[1].tv_usec = 0;
+                       if (utimes(dst, tv) == -1)
+                               error("Can't set times on \"%s\": %s",
+                                   dst, strerror(errno));
+               } else
+                       debug("Server did not send times for directory "
+                           "\"%s\"", dst);
+       }
+
+       free_sftp_dirents(dir_entries);
+
+       return ret;
+}
+
+int
+download_dir(struct sftp_conn *conn, char *src, char *dst,
+    Attrib *dirattrib, int pflag, int printflag)
+{
+       char *src_canon;
+       int ret;
+
+       if ((src_canon = do_realpath(conn, src)) == NULL) {
+               error("Unable to canonicalise path \"%s\"", src);
+               return -1;
+       }
+
+       ret = download_dir_internal(conn, src_canon, dst,
+           dirattrib, pflag, printflag, 0);
+       xfree(src_canon);
+       return ret;
+}
+
 int
 do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
     int pflag)
@@ -1195,7 +1331,8 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
 
        buffer_clear(&msg);
 
-       handle = get_handle(conn->fd_in, id, &handle_len);
+       handle = get_handle(conn->fd_in, id, &handle_len,
+           "remote open(\"%s\")", remote_path);
        if (handle == NULL) {
                close(local_fd);
                buffer_free(&msg);
@@ -1223,7 +1360,8 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
                        len = 0;
                else do
                        len = read(local_fd, data, conn->transfer_buflen);
-               while ((len == -1) && (errno == EINTR || errno == EAGAIN));
+               while ((len == -1) &&
+                   (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
 
                if (len == -1)
                        fatal("Couldn't read from \"%s\": %s", local_path,
@@ -1312,3 +1450,127 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
 
        return status;
 }
+
+static int
+upload_dir_internal(struct sftp_conn *conn, char *src, char *dst,
+    int pflag, int printflag, int depth)
+{
+       int ret = 0, status;
+       DIR *dirp;
+       struct dirent *dp;
+       char *filename, *new_src, *new_dst;
+       struct stat sb;
+       Attrib a;
+
+       if (depth >= MAX_DIR_DEPTH) {
+               error("Maximum directory depth exceeded: %d levels", depth);
+               return -1;
+       }
+
+       if (stat(src, &sb) == -1) {
+               error("Couldn't stat directory \"%s\": %s",
+                   src, strerror(errno));
+               return -1;
+       }
+       if (!S_ISDIR(sb.st_mode)) {
+               error("\"%s\" is not a directory", src);
+               return -1;
+       }
+       if (printflag)
+               printf("Entering %s\n", src);
+
+       attrib_clear(&a);
+       stat_to_attrib(&sb, &a);
+       a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
+       a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
+       a.perm &= 01777;
+       if (!pflag)
+               a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
+       
+       status = do_mkdir(conn, dst, &a, 0);
+       /*
+        * we lack a portable status for errno EEXIST,
+        * so if we get a SSH2_FX_FAILURE back we must check
+        * if it was created successfully.
+        */
+       if (status != SSH2_FX_OK) {
+               if (status != SSH2_FX_FAILURE)
+                       return -1;
+               if (do_stat(conn, dst, 0) == NULL) 
+                       return -1;
+       }
+
+       if ((dirp = opendir(src)) == NULL) {
+               error("Failed to open dir \"%s\": %s", src, strerror(errno));
+               return -1;
+       }
+       
+       while (((dp = readdir(dirp)) != NULL) && !interrupted) {
+               if (dp->d_ino == 0)
+                       continue;
+               filename = dp->d_name;
+               new_dst = path_append(dst, filename);
+               new_src = path_append(src, filename);
+
+               if (lstat(new_src, &sb) == -1) {
+                       logit("%s: lstat failed: %s", filename,
+                           strerror(errno));
+                       ret = -1;
+               } else if (S_ISDIR(sb.st_mode)) {
+                       if (strcmp(filename, ".") == 0 ||
+                           strcmp(filename, "..") == 0)
+                               continue;
+
+                       if (upload_dir_internal(conn, new_src, new_dst,
+                           pflag, depth + 1, printflag) == -1)
+                               ret = -1;
+               } else if (S_ISREG(sb.st_mode)) {
+                       if (do_upload(conn, new_src, new_dst, pflag) == -1) {
+                               error("Uploading of file %s to %s failed!",
+                                   new_src, new_dst);
+                               ret = -1;
+                       }
+               } else
+                       logit("%s: not a regular file\n", filename);
+               xfree(new_dst);
+               xfree(new_src);
+       }
+
+       do_setstat(conn, dst, &a);
+
+       (void) closedir(dirp);
+       return ret;
+}
+
+int
+upload_dir(struct sftp_conn *conn, char *src, char *dst, int printflag,
+    int pflag)
+{
+       char *dst_canon;
+       int ret;
+
+       if ((dst_canon = do_realpath(conn, dst)) == NULL) {
+               error("Unable to canonicalise path \"%s\"", dst);
+               return -1;
+       }
+
+       ret = upload_dir_internal(conn, src, dst_canon, pflag, printflag, 0);
+       xfree(dst_canon);
+       return ret;
+}
+
+char *
+path_append(char *p1, char *p2)
+{
+       char *ret;
+       size_t len = strlen(p1) + strlen(p2) + 2;
+
+       ret = xmalloc(len);
+       strlcpy(ret, p1, len);
+       if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
+               strlcat(ret, "/", len);
+       strlcat(ret, p2, len);
+
+       return(ret);
+}
+
This page took 0.056377 seconds and 4 git commands to generate.