]> andersk Git - openssh.git/blobdiff - sftp-client.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / sftp-client.c
index 0d42696ae4459a2e6cf4586c2a035c89e1639999..6124c0f408cc2ed731786a28b3bdcb47b069a4b1 100644 (file)
@@ -1,25 +1,18 @@
+/* $OpenBSD: sftp-client.c,v 1.90 2009/10/11 10:41:26 dtucker Exp $ */
 /*
- * Copyright (c) 2001,2002 Damien Miller.  All rights reserved.
+ * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
  *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
 /* XXX: memleaks */
 /* XXX: copy between two remote sites */
 
 #include "includes.h"
-RCSID("$OpenBSD: sftp-client.c,v 1.31 2002/04/06 00:30:08 djm Exp $");
 
-#include "openbsd-compat/fake-queue.h"
+#include <sys/types.h>
+#include <sys/param.h>
+#ifdef HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+#endif
+#include "openbsd-compat/sys-queue.h"
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#include <sys/uio.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
 
-#include "buffer.h"
-#include "bufaux.h"
-#include "getput.h"
 #include "xmalloc.h"
+#include "buffer.h"
 #include "log.h"
 #include "atomicio.h"
+#include "progressmeter.h"
+#include "misc.h"
 
 #include "sftp.h"
 #include "sftp-common.h"
 #include "sftp-client.h"
 
-/* Minimum amount of data to read at at time */
+extern volatile sig_atomic_t interrupted;
+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;
@@ -53,52 +72,61 @@ struct sftp_conn {
        u_int num_requests;
        u_int version;
        u_int msg_id;
+#define SFTP_EXT_POSIX_RENAME  0x00000001
+#define SFTP_EXT_STATVFS       0x00000002
+#define SFTP_EXT_FSTATVFS      0x00000004
+       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)
 {
-       int mlen = buffer_len(m);
-       int len;
-       Buffer oqueue;
+       u_char mlen[4];
+       struct iovec iov[2];
+
+       if (buffer_len(m) > SFTP_MAX_MSG_LENGTH)
+               fatal("Outbound message too long %u", buffer_len(m));
 
-       buffer_init(&oqueue);
-       buffer_put_int(&oqueue, mlen);
-       buffer_append(&oqueue, buffer_ptr(m), mlen);
-       buffer_consume(m, mlen);
+       /* Send length first */
+       put_u32(mlen, buffer_len(m));
+       iov[0].iov_base = mlen;
+       iov[0].iov_len = sizeof(mlen);
+       iov[1].iov_base = buffer_ptr(m);
+       iov[1].iov_len = buffer_len(m);
 
-       len = atomicio(write, fd, buffer_ptr(&oqueue), buffer_len(&oqueue));
-       if (len <= 0)
+       if (atomiciov(writev, fd, iov, 2) != buffer_len(m) + sizeof(mlen))
                fatal("Couldn't send packet: %s", strerror(errno));
 
-       buffer_free(&oqueue);
+       buffer_clear(m);
 }
 
 static void
 get_msg(int fd, Buffer *m)
 {
-       u_int len, msg_len;
-       unsigned char buf[4096];
-
-       len = atomicio(read, fd, buf, 4);
-       if (len == 0)
-               fatal("Connection closed");
-       else if (len == -1)
-               fatal("Couldn't read packet: %s", strerror(errno));
-
-       msg_len = GET_32BIT(buf);
-       if (msg_len > 256 * 1024)
-               fatal("Received message too long %d", msg_len);
-
-       while (msg_len) {
-               len = atomicio(read, fd, buf, MIN(msg_len, sizeof(buf)));
-               if (len == 0)
+       u_int msg_len;
+
+       buffer_append_space(m, 4);
+       if (atomicio(read, fd, buffer_ptr(m), 4) != 4) {
+               if (errno == EPIPE)
                        fatal("Connection closed");
-               else if (len == -1)
+               else
                        fatal("Couldn't read packet: %s", strerror(errno));
+       }
+
+       msg_len = buffer_get_int(m);
+       if (msg_len > SFTP_MAX_MSG_LENGTH)
+               fatal("Received message too long %u", msg_len);
 
-               msg_len -= len;
-               buffer_append(m, buf, len);
+       buffer_append_space(m, msg_len);
+       if (atomicio(read, fd, buffer_ptr(m), msg_len) != msg_len) {
+               if (errno == EPIPE)
+                       fatal("Connection closed");
+               else
+                       fatal("Read packet: %s", strerror(errno));
        }
 }
 
@@ -113,7 +141,7 @@ send_string_request(int fd, u_int id, u_int code, char *s,
        buffer_put_int(&msg, id);
        buffer_put_string(&msg, s, len);
        send_msg(fd, &msg);
-       debug3("Sent message fd %d T:%d I:%d", fd, code, id);
+       debug3("Sent message fd %d T:%u I:%u", fd, code, id);
        buffer_free(&msg);
 }
 
@@ -129,12 +157,12 @@ send_string_attrs_request(int fd, u_int id, u_int code, char *s,
        buffer_put_string(&msg, s, len);
        encode_attrib(&msg, a);
        send_msg(fd, &msg);
-       debug3("Sent message fd %d T:%d I:%d", fd, code, id);
+       debug3("Sent message fd %d T:%u I:%u", fd, code, id);
        buffer_free(&msg);
 }
 
 static u_int
-get_status(int fd, int expected_id)
+get_status(int fd, u_int expected_id)
 {
        Buffer msg;
        u_int type, id, status;
@@ -145,25 +173,32 @@ get_status(int fd, int expected_id)
        id = buffer_get_int(&msg);
 
        if (id != expected_id)
-               fatal("ID mismatch (%d != %d)", id, expected_id);
+               fatal("ID mismatch (%u != %u)", id, expected_id);
        if (type != SSH2_FXP_STATUS)
-               fatal("Expected SSH2_FXP_STATUS(%d) packet, got %d",
+               fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u",
                    SSH2_FXP_STATUS, type);
 
        status = buffer_get_int(&msg);
        buffer_free(&msg);
 
-       debug3("SSH2_FXP_STATUS %d", status);
+       debug3("SSH2_FXP_STATUS %u", status);
 
        return(status);
 }
 
 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);
@@ -171,15 +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 (%d != %d)", 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(%d) packet, got %d",
-                   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);
@@ -200,9 +237,9 @@ get_decode_stat(int fd, u_int expected_id, int quiet)
        type = buffer_get_char(&msg);
        id = buffer_get_int(&msg);
 
-       debug3("Received stat reply T:%d I:%d", type, id);
+       debug3("Received stat reply T:%u I:%u", type, id);
        if (id != expected_id)
-               fatal("ID mismatch (%d != %d)", id, expected_id);
+               fatal("ID mismatch (%u != %u)", id, expected_id);
        if (type == SSH2_FXP_STATUS) {
                int status = buffer_get_int(&msg);
 
@@ -210,9 +247,10 @@ get_decode_stat(int fd, u_int expected_id, int quiet)
                        debug("Couldn't stat remote file: %s", fx2txt(status));
                else
                        error("Couldn't stat remote file: %s", fx2txt(status));
+               buffer_free(&msg);
                return(NULL);
        } else if (type != SSH2_FXP_ATTRS) {
-               fatal("Expected SSH2_FXP_ATTRS(%d) packet, got %d",
+               fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u",
                    SSH2_FXP_ATTRS, type);
        }
        a = decode_attrib(&msg);
@@ -221,10 +259,62 @@ get_decode_stat(int fd, u_int expected_id, int quiet)
        return(a);
 }
 
+static int
+get_decode_statvfs(int fd, struct sftp_statvfs *st, u_int expected_id,
+    int quiet)
+{
+       Buffer msg;
+       u_int type, id, flag;
+
+       buffer_init(&msg);
+       get_msg(fd, &msg);
+
+       type = buffer_get_char(&msg);
+       id = buffer_get_int(&msg);
+
+       debug3("Received statvfs reply T:%u I:%u", type, id);
+       if (id != expected_id)
+               fatal("ID mismatch (%u != %u)", id, expected_id);
+       if (type == SSH2_FXP_STATUS) {
+               int status = buffer_get_int(&msg);
+
+               if (quiet)
+                       debug("Couldn't statvfs: %s", fx2txt(status));
+               else
+                       error("Couldn't statvfs: %s", fx2txt(status));
+               buffer_free(&msg);
+               return -1;
+       } else if (type != SSH2_FXP_EXTENDED_REPLY) {
+               fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u",
+                   SSH2_FXP_EXTENDED_REPLY, type);
+       }
+
+       bzero(st, sizeof(*st));
+       st->f_bsize = buffer_get_int64(&msg);
+       st->f_frsize = buffer_get_int64(&msg);
+       st->f_blocks = buffer_get_int64(&msg);
+       st->f_bfree = buffer_get_int64(&msg);
+       st->f_bavail = buffer_get_int64(&msg);
+       st->f_files = buffer_get_int64(&msg);
+       st->f_ffree = buffer_get_int64(&msg);
+       st->f_favail = buffer_get_int64(&msg);
+       st->f_fsid = buffer_get_int64(&msg);
+       flag = buffer_get_int64(&msg);
+       st->f_namemax = buffer_get_int64(&msg);
+
+       st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0;
+       st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0;
+
+       buffer_free(&msg);
+
+       return 0;
+}
+
 struct sftp_conn *
 do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
 {
-       int type, version;
+       u_int type, exts = 0;
+       int version;
        Buffer msg;
        struct sftp_conn *ret;
 
@@ -239,7 +329,7 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
 
        /* Expecting a VERSION reply */
        if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
-               error("Invalid packet back from SSH2_FXP_INIT (type %d)",
+               error("Invalid packet back from SSH2_FXP_INIT (type %u)",
                    type);
                buffer_free(&msg);
                return(NULL);
@@ -252,8 +342,27 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
        while (buffer_len(&msg) > 0) {
                char *name = buffer_get_string(&msg, NULL);
                char *value = buffer_get_string(&msg, NULL);
-
-               debug2("Init extension: \"%s\"", name);
+               int known = 0;
+
+               if (strcmp(name, "posix-rename@openssh.com") == 0 &&
+                   strcmp(value, "1") == 0) {
+                       exts |= SFTP_EXT_POSIX_RENAME;
+                       known = 1;
+               } else if (strcmp(name, "statvfs@openssh.com") == 0 &&
+                   strcmp(value, "2") == 0) {
+                       exts |= SFTP_EXT_STATVFS;
+                       known = 1;
+               } if (strcmp(name, "fstatvfs@openssh.com") == 0 &&
+                   strcmp(value, "2") == 0) {
+                       exts |= SFTP_EXT_FSTATVFS;
+                       known = 1;
+               }
+               if (known) {
+                       debug2("Server supports extension \"%s\" revision %s",
+                           name, value);
+               } else {
+                       debug2("Unrecognised server extension \"%s\"", name);
+               }
                xfree(name);
                xfree(value);
        }
@@ -267,6 +376,7 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests)
        ret->num_requests = num_requests;
        ret->version = version;
        ret->msg_id = 1;
+       ret->exts = exts;
 
        /* Some filexfer v.0 servers don't support large packets */
        if (version == 0)
@@ -294,7 +404,7 @@ do_close(struct sftp_conn *conn, char *handle, u_int handle_len)
        buffer_put_int(&msg, id);
        buffer_put_string(&msg, handle, handle_len);
        send_msg(conn->fd_out, &msg);
-       debug3("Sent message SSH2_FXP_CLOSE I:%d", id);
+       debug3("Sent message SSH2_FXP_CLOSE I:%u", id);
 
        status = get_status(conn->fd_in, id);
        if (status != SSH2_FX_OK)
@@ -311,7 +421,7 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
     SFTP_DIRENT ***dir)
 {
        Buffer msg;
-       u_int type, id, handle_len, i, expected_id, ents = 0;
+       u_int count, type, id, handle_len, i, expected_id, ents = 0;
        char *handle;
 
        id = conn->msg_id++;
@@ -324,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);
 
@@ -334,12 +445,10 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
                (*dir)[0] = NULL;
        }
 
-       for (;;) {
-               int count;
-
+       for (; !interrupted;) {
                id = expected_id = conn->msg_id++;
 
-               debug3("Sending SSH2_FXP_READDIR I:%d", id);
+               debug3("Sending SSH2_FXP_READDIR I:%u", id);
 
                buffer_clear(&msg);
                buffer_put_char(&msg, SSH2_FXP_READDIR);
@@ -354,10 +463,10 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
                type = buffer_get_char(&msg);
                id = buffer_get_int(&msg);
 
-               debug3("Received reply T:%d I:%d", type, id);
+               debug3("Received reply T:%u I:%u", type, id);
 
                if (id != expected_id)
-                       fatal("ID mismatch (%d != %d)", id, expected_id);
+                       fatal("ID mismatch (%u != %u)", id, expected_id);
 
                if (type == SSH2_FXP_STATUS) {
                        int status = buffer_get_int(&msg);
@@ -370,10 +479,11 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
                                error("Couldn't read directory: %s",
                                    fx2txt(status));
                                do_close(conn, handle, handle_len);
+                               xfree(handle);
                                return(status);
                        }
                } else if (type != SSH2_FXP_NAME)
-                       fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
+                       fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
                            SSH2_FXP_NAME, type);
 
                count = buffer_get_int(&msg);
@@ -391,16 +501,26 @@ 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, sizeof(**dir) *
-                                   (ents + 2));
+                               *dir = xrealloc(*dir, ents + 2, sizeof(**dir));
                                (*dir)[ents] = xmalloc(sizeof(***dir));
                                (*dir)[ents]->filename = xstrdup(filename);
                                (*dir)[ents]->longname = xstrdup(longname);
                                memcpy(&(*dir)[ents]->a, a, sizeof(*a));
                                (*dir)[++ents] = NULL;
                        }
-
+ next:
                        xfree(filename);
                        xfree(longname);
                }
@@ -410,13 +530,14 @@ do_lsreaddir(struct sftp_conn *conn, char *path, int printflag,
        do_close(conn, handle, handle_len);
        xfree(handle);
 
-       return(0);
-}
+       /* Don't return partial matches on interrupt */
+       if (interrupted && dir != NULL && *dir != NULL) {
+               free_sftp_dirents(*dir);
+               *dir = xmalloc(sizeof(**dir));
+               **dir = NULL;
+       }
 
-int
-do_ls(struct sftp_conn *conn, char *path)
-{
-       return(do_lsreaddir(conn, path, 1, NULL));
+       return(0);
 }
 
 int
@@ -454,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;
 
@@ -463,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);
@@ -508,7 +629,7 @@ do_lstat(struct sftp_conn *conn, char *path, int quiet)
                if (quiet)
                        debug("Server version does not support lstat operation");
                else
-                       log("Server version does not support lstat operation");
+                       logit("Server version does not support lstat operation");
                return(do_stat(conn, path, quiet));
        }
 
@@ -519,6 +640,7 @@ do_lstat(struct sftp_conn *conn, char *path, int quiet)
        return(get_decode_stat(conn->fd_in, id, quiet));
 }
 
+#ifdef notyet
 Attrib *
 do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
 {
@@ -530,6 +652,7 @@ do_fstat(struct sftp_conn *conn, char *handle, u_int handle_len, int quiet)
 
        return(get_decode_stat(conn->fd_in, id, quiet));
 }
+#endif
 
 int
 do_setstat(struct sftp_conn *conn, char *path, Attrib *a)
@@ -584,7 +707,7 @@ do_realpath(struct sftp_conn *conn, char *path)
        id = buffer_get_int(&msg);
 
        if (id != expected_id)
-               fatal("ID mismatch (%d != %d)", id, expected_id);
+               fatal("ID mismatch (%u != %u)", id, expected_id);
 
        if (type == SSH2_FXP_STATUS) {
                u_int status = buffer_get_int(&msg);
@@ -592,7 +715,7 @@ do_realpath(struct sftp_conn *conn, char *path)
                error("Couldn't canonicalise: %s", fx2txt(status));
                return(NULL);
        } else if (type != SSH2_FXP_NAME)
-               fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
+               fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
                    SSH2_FXP_NAME, type);
 
        count = buffer_get_int(&msg);
@@ -622,13 +745,20 @@ do_rename(struct sftp_conn *conn, char *oldpath, char *newpath)
 
        /* Send rename request */
        id = conn->msg_id++;
-       buffer_put_char(&msg, SSH2_FXP_RENAME);
-       buffer_put_int(&msg, id);
+       if ((conn->exts & SFTP_EXT_POSIX_RENAME)) {
+               buffer_put_char(&msg, SSH2_FXP_EXTENDED);
+               buffer_put_int(&msg, id);
+               buffer_put_cstring(&msg, "posix-rename@openssh.com");
+       } else {
+               buffer_put_char(&msg, SSH2_FXP_RENAME);
+               buffer_put_int(&msg, id);
+       }
        buffer_put_cstring(&msg, oldpath);
        buffer_put_cstring(&msg, newpath);
        send_msg(conn->fd_out, &msg);
-       debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath,
-           newpath);
+       debug3("Sent message %s \"%s\" -> \"%s\"",
+           (conn->exts & SFTP_EXT_POSIX_RENAME) ? "posix-rename@openssh.com" :
+           "SSH2_FXP_RENAME", oldpath, newpath);
        buffer_free(&msg);
 
        status = get_status(conn->fd_in, id);
@@ -652,7 +782,7 @@ do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
 
        buffer_init(&msg);
 
-       /* Send rename request */
+       /* Send symlink request */
        id = conn->msg_id++;
        buffer_put_char(&msg, SSH2_FXP_SYMLINK);
        buffer_put_int(&msg, id);
@@ -665,12 +795,13 @@ do_symlink(struct sftp_conn *conn, char *oldpath, char *newpath)
 
        status = get_status(conn->fd_in, id);
        if (status != SSH2_FX_OK)
-               error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath,
+               error("Couldn't symlink file \"%s\" to \"%s\": %s", oldpath,
                    newpath, fx2txt(status));
 
        return(status);
 }
 
+#ifdef notyet
 char *
 do_readlink(struct sftp_conn *conn, char *path)
 {
@@ -690,7 +821,7 @@ do_readlink(struct sftp_conn *conn, char *path)
        id = buffer_get_int(&msg);
 
        if (id != expected_id)
-               fatal("ID mismatch (%d != %d)", id, expected_id);
+               fatal("ID mismatch (%u != %u)", id, expected_id);
 
        if (type == SSH2_FXP_STATUS) {
                u_int status = buffer_get_int(&msg);
@@ -698,7 +829,7 @@ do_readlink(struct sftp_conn *conn, char *path)
                error("Couldn't readlink: %s", fx2txt(status));
                return(NULL);
        } else if (type != SSH2_FXP_NAME)
-               fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
+               fatal("Expected SSH2_FXP_NAME(%u) packet, got %u",
                    SSH2_FXP_NAME, type);
 
        count = buffer_get_int(&msg);
@@ -717,6 +848,61 @@ do_readlink(struct sftp_conn *conn, char *path)
 
        return(filename);
 }
+#endif
+
+int
+do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st,
+    int quiet)
+{
+       Buffer msg;
+       u_int id;
+
+       if ((conn->exts & SFTP_EXT_STATVFS) == 0) {
+               error("Server does not support statvfs@openssh.com extension");
+               return -1;
+       }
+
+       id = conn->msg_id++;
+
+       buffer_init(&msg);
+       buffer_clear(&msg);
+       buffer_put_char(&msg, SSH2_FXP_EXTENDED);
+       buffer_put_int(&msg, id);
+       buffer_put_cstring(&msg, "statvfs@openssh.com");
+       buffer_put_cstring(&msg, path);
+       send_msg(conn->fd_out, &msg);
+       buffer_free(&msg);
+
+       return get_decode_statvfs(conn->fd_in, st, id, quiet);
+}
+
+#ifdef notyet
+int
+do_fstatvfs(struct sftp_conn *conn, const char *handle, u_int handle_len,
+    struct sftp_statvfs *st, int quiet)
+{
+       Buffer msg;
+       u_int id;
+
+       if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) {
+               error("Server does not support fstatvfs@openssh.com extension");
+               return -1;
+       }
+
+       id = conn->msg_id++;
+
+       buffer_init(&msg);
+       buffer_clear(&msg);
+       buffer_put_char(&msg, SSH2_FXP_EXTENDED);
+       buffer_put_int(&msg, id);
+       buffer_put_cstring(&msg, "fstatvfs@openssh.com");
+       buffer_put_string(&msg, handle, handle_len);
+       send_msg(conn->fd_out, &msg);
+       buffer_free(&msg);
+
+       return get_decode_statvfs(conn->fd_in, st, id, quiet);
+}
+#endif
 
 static void
 send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
@@ -737,15 +923,16 @@ 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, num_req, max_req, write_error;
+       int local_fd, status = 0, write_error;
        int read_error, write_errno;
        u_int64_t offset, size;
-       u_int handle_len, mode, type, id, buflen;
+       u_int handle_len, mode, type, id, buflen, num_req, max_req;
+       off_t progress_counter;
        struct request {
                u_int id;
                u_int len;
@@ -757,19 +944,18 @@ 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 = S_IWRITE | (a->perm & 0777);
+               mode = a->perm & 0777;
        else
                mode = 0666;
 
        if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
-           (a->perm & S_IFDIR)) {
-               error("Cannot download a directory: %s", remote_path);
+           (!S_ISREG(a->perm))) {
+               error("Cannot download non-regular file: %s", remote_path);
                return(-1);
        }
 
@@ -790,18 +976,21 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
        attrib_clear(&junk); /* Send empty attributes */
        encode_attrib(&msg, &junk);
        send_msg(conn->fd_out, &msg);
-       debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
+       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);
        }
 
-       local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+       local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC,
+           mode | S_IWRITE);
        if (local_fd == -1) {
                error("Couldn't open local file \"%s\" for writing: %s",
                    local_path, strerror(errno));
+               do_close(conn, handle, handle_len);
                buffer_free(&msg);
                xfree(handle);
                return(-1);
@@ -810,10 +999,25 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
        /* Read from remote and write to local */
        write_error = read_error = write_errno = num_req = offset = 0;
        max_req = 1;
+       progress_counter = 0;
+
+       if (showprogress && size != 0)
+               start_progress_meter(remote_path, size, &progress_counter);
+
        while (num_req > 0 || max_req > 0) {
                char *data;
                u_int len;
 
+               /*
+                * Simulate EOF on interrupt: stop sending new requests and
+                * allow outstanding requests to drain gracefully
+                */
+               if (interrupted) {
+                       if (num_req == 0) /* If we haven't started yet... */
+                               break;
+                       max_req = 0;
+               }
+
                /* Send some more requests */
                while (num_req < max_req) {
                        debug3("Request range %llu -> %llu (%d/%d)",
@@ -835,10 +1039,10 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
                get_msg(conn->fd_in, &msg);
                type = buffer_get_char(&msg);
                id = buffer_get_int(&msg);
-               debug3("Received reply T:%d I:%d R:%d", type, id, max_req);
+               debug3("Received reply T:%u I:%u R:%d", type, id, max_req);
 
                /* Find the request in our queue */
-               for(req = TAILQ_FIRST(&requests);
+               for (req = TAILQ_FIRST(&requests);
                    req != NULL && req->id != id;
                    req = TAILQ_NEXT(req, tq))
                        ;
@@ -862,14 +1066,15 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
                            (unsigned long long)req->offset + len - 1);
                        if (len > req->len)
                                fatal("Received more data than asked for "
-                                     "%d > %d", len, req->len);
+                                   "%u > %u", len, req->len);
                        if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
-                            atomicio(write, local_fd, data, len) != len) &&
+                           atomicio(vwrite, local_fd, data, len) != len) &&
                            !write_error) {
                                write_errno = errno;
                                write_error = 1;
                                max_req = 0;
                        }
+                       progress_counter += len;
                        xfree(data);
 
                        if (len == req->len) {
@@ -900,18 +1105,20 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
                                            (unsigned long long)offset,
                                            num_req);
                                        max_req = 1;
-                               }
-                               else if (max_req < conn->num_requests + 1) {
+                               } else if (max_req <= conn->num_requests) {
                                        ++max_req;
                                }
                        }
                        break;
                default:
-                       fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
+                       fatal("Expected SSH2_FXP_DATA(%u) packet, got %u",
                            SSH2_FXP_DATA, type);
                }
        }
 
+       if (showprogress && size)
+               stop_progress_meter();
+
        /* Sanity check */
        if (TAILQ_FIRST(&requests) != NULL)
                fatal("Transfer complete, but requests still in queue");
@@ -931,11 +1138,11 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
                /* Override umask and utimes if asked */
 #ifdef HAVE_FCHMOD
                if (pflag && fchmod(local_fd, mode) == -1)
-#else 
+#else
                if (pflag && chmod(local_path, mode) == -1)
 #endif /* HAVE_FCHMOD */
                        error("Couldn't set mode on \"%s\": %s", local_path,
-                             strerror(errno));
+                           strerror(errno));
                if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
                        struct timeval tv[2];
                        tv[0].tv_sec = a->atime;
@@ -943,7 +1150,7 @@ do_download(struct sftp_conn *conn, char *remote_path, char *local_path,
                        tv[0].tv_usec = tv[1].tv_usec = 0;
                        if (utimes(local_path, tv) == -1)
                                error("Can't set times on \"%s\": %s",
-                                     local_path, strerror(errno));
+                                   local_path, strerror(errno));
                }
        }
        close(local_fd);
@@ -953,13 +1160,122 @@ 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)
 {
-       int local_fd, status;
+       int local_fd;
+       int status = SSH2_FX_OK;
        u_int handle_len, id, type;
-       u_int64_t offset;
+       off_t offset;
        char *handle, *data;
        Buffer msg;
        struct stat sb;
@@ -969,11 +1285,11 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
        struct outstanding_ack {
                u_int id;
                u_int len;
-               u_int64_t offset;
+               off_t offset;
                TAILQ_ENTRY(outstanding_ack) tq;
        };
        TAILQ_HEAD(ackhead, outstanding_ack) acks;
-       struct outstanding_ack *ack;
+       struct outstanding_ack *ack = NULL;
 
        TAILQ_INIT(&acks);
 
@@ -988,6 +1304,11 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
                close(local_fd);
                return(-1);
        }
+       if (!S_ISREG(sb.st_mode)) {
+               error("%s is not a regular file", local_path);
+               close(local_fd);
+               return(-1);
+       }
        stat_to_attrib(&sb, &a);
 
        a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
@@ -1006,15 +1327,16 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
        buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
        encode_attrib(&msg, &a);
        send_msg(conn->fd_out, &msg);
-       debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
+       debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, 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);
-               return(-1);
+               return -1;
        }
 
        startid = ackid = id + 1;
@@ -1022,16 +1344,24 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
 
        /* Read from local and write to remote */
        offset = 0;
+       if (showprogress)
+               start_progress_meter(local_path, sb.st_size, &offset);
+
        for (;;) {
                int len;
 
                /*
-                * Can't use atomicio here because it returns 0 on EOF, thus losing
-                * the last block of the file
+                * Can't use atomicio here because it returns 0 on EOF,
+                * thus losing the last block of the file.
+                * Simulate an EOF on interrupt, allowing ACKs from the
+                * server to drain.
                 */
-               do
+               if (interrupted || status != SSH2_FX_OK)
+                       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,
@@ -1051,8 +1381,8 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
                        buffer_put_int64(&msg, offset);
                        buffer_put_string(&msg, data, len);
                        send_msg(conn->fd_out, &msg);
-                       debug3("Sent message SSH2_FXP_WRITE I:%d O:%llu S:%u",
-                              id, (unsigned long long)offset, len);
+                       debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u",
+                           id, (unsigned long long)offset, len);
                } else if (TAILQ_FIRST(&acks) == NULL)
                        break;
 
@@ -1061,7 +1391,7 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
 
                if (id == startid || len == 0 ||
                    id - ackid >= conn->num_requests) {
-                       u_int r_id;
+                       u_int r_id;
 
                        buffer_clear(&msg);
                        get_msg(conn->fd_in, &msg);
@@ -1076,46 +1406,171 @@ do_upload(struct sftp_conn *conn, char *local_path, char *remote_path,
                        debug3("SSH2_FXP_STATUS %d", status);
 
                        /* Find the request in our queue */
-                       for(ack = TAILQ_FIRST(&acks);
+                       for (ack = TAILQ_FIRST(&acks);
                            ack != NULL && ack->id != r_id;
                            ack = TAILQ_NEXT(ack, tq))
                                ;
                        if (ack == NULL)
-                               fatal("Can't find request for ID %d", r_id);
+                               fatal("Can't find request for ID %u", r_id);
                        TAILQ_REMOVE(&acks, ack, tq);
-
-                       if (status != SSH2_FX_OK) {
-                               error("Couldn't write to remote file \"%s\": %s",
-                                     remote_path, fx2txt(status));
-                               do_close(conn, handle, handle_len);
-                               close(local_fd);
-                               goto done;
-                       }
-                       debug3("In write loop, ack for %u %d bytes at %llu",
-                          ack->id, ack->len, (unsigned long long)ack->offset);
+                       debug3("In write loop, ack for %u %u bytes at %lld",
+                           ack->id, ack->len, (long long)ack->offset);
                        ++ackid;
-                       free(ack);
+                       xfree(ack);
                }
                offset += len;
+               if (offset < 0)
+                       fatal("%s: offset < 0", __func__);
        }
+       buffer_free(&msg);
+
+       if (showprogress)
+               stop_progress_meter();
        xfree(data);
 
+       if (status != SSH2_FX_OK) {
+               error("Couldn't write to remote file \"%s\": %s",
+                   remote_path, fx2txt(status));
+               status = -1;
+       }
+
        if (close(local_fd) == -1) {
                error("Couldn't close local file \"%s\": %s", local_path,
                    strerror(errno));
-               do_close(conn, handle, handle_len);
                status = -1;
-               goto done;
        }
 
        /* Override umask and utimes if asked */
        if (pflag)
                do_fsetstat(conn, handle, handle_len, &a);
 
-       status = do_close(conn, handle, handle_len);
-
-done:
+       if (do_close(conn, handle, handle_len) != SSH2_FX_OK)
+               status = -1;
        xfree(handle);
-       buffer_free(&msg);
-       return(status);
+
+       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.211206 seconds and 4 git commands to generate.