PSCP now uses the modern SFTP protocol if it can, and falls back to
[u/mdw/putty] / scp.c
diff --git a/scp.c b/scp.c
index 9a2624a..cd4fff7 100644 (file)
--- a/scp.c
+++ b/scp.c
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <limits.h>
 #include <time.h>
 #include <assert.h>
 /* GUI Adaptation - Sept 2000 */
 #include <time.h>
 #include <assert.h>
 /* GUI Adaptation - Sept 2000 */
@@ -27,6 +28,8 @@
 
 #define PUTTY_DO_GLOBALS
 #include "putty.h"
 
 #define PUTTY_DO_GLOBALS
 #include "putty.h"
+#include "ssh.h"
+#include "sftp.h"
 #include "winstuff.h"
 #include "storage.h"
 
 #include "winstuff.h"
 #include "storage.h"
 
 #define   WM_RET_ERR_CNT       ( WM_APP_BASE+406 )
 #define   WM_LS_RET_ERR_CNT    ( WM_APP_BASE+407 )
 
 #define   WM_RET_ERR_CNT       ( WM_APP_BASE+406 )
 #define   WM_LS_RET_ERR_CNT    ( WM_APP_BASE+407 )
 
+static int list = 0;
 static int verbose = 0;
 static int recursive = 0;
 static int preserve = 0;
 static int targetshouldbedirectory = 0;
 static int statistics = 1;
 static int portnumber = 0;
 static int verbose = 0;
 static int recursive = 0;
 static int preserve = 0;
 static int targetshouldbedirectory = 0;
 static int statistics = 1;
 static int portnumber = 0;
+static int prev_stats_len = 0;
 static char *password = NULL;
 static int errs = 0;
 /* GUI Adaptation - Sept 2000 */
 static char *password = NULL;
 static int errs = 0;
 /* GUI Adaptation - Sept 2000 */
@@ -62,6 +67,7 @@ static int statperct = 0;
 static unsigned long statelapsed = 0;
 static int gui_mode = 0;
 static char *gui_hwnd = NULL;
 static unsigned long statelapsed = 0;
 static int gui_mode = 0;
 static char *gui_hwnd = NULL;
+static int using_sftp = 0;
 
 static void source(char *src);
 static void rsource(char *src);
 
 static void source(char *src);
 static void rsource(char *src);
@@ -70,11 +76,15 @@ static void sink(char *targ, char *src);
 static void tell_char(FILE * stream, char c);
 static void tell_str(FILE * stream, char *str);
 static void tell_user(FILE * stream, char *fmt, ...);
 static void tell_char(FILE * stream, char c);
 static void tell_str(FILE * stream, char *str);
 static void tell_user(FILE * stream, char *fmt, ...);
-static void send_char_msg(unsigned int msg_id, char c);
-static void send_str_msg(unsigned int msg_id, char *str);
 static void gui_update_stats(char *name, unsigned long size,
                             int percentage, unsigned long elapsed);
 
 static void gui_update_stats(char *name, unsigned long size,
                             int percentage, unsigned long elapsed);
 
+/*
+ * The maximum amount of queued data we accept before we stop and
+ * wait for the server to process some.
+ */
+#define MAX_SCP_BUFSIZE 16384
+
 void logevent(char *string)
 {
 }
 void logevent(char *string)
 {
 }
@@ -94,6 +104,8 @@ void verify_ssh_host_key(char *host, int port, char *keytype,
                         char *keystr, char *fingerprint)
 {
     int ret;
                         char *keystr, char *fingerprint)
 {
     int ret;
+    HANDLE hin;
+    DWORD savemode, i;
 
     static const char absentmsg[] =
        "The server's host key is not cached in the registry. You\n"
 
     static const char absentmsg[] =
        "The server's host key is not cached in the registry. You\n"
@@ -103,8 +115,11 @@ void verify_ssh_host_key(char *host, int port, char *keytype,
        "%s\n"
        "If you trust this host, enter \"y\" to add the key to\n"
        "PuTTY's cache and carry on connecting.\n"
        "%s\n"
        "If you trust this host, enter \"y\" to add the key to\n"
        "PuTTY's cache and carry on connecting.\n"
-       "If you do not trust this host, enter \"n\" to abandon the\n"
-       "connection.\n" "Continue connecting? (y/n) ";
+       "If you want to carry on connecting just once, without\n"
+       "adding the key to the cache, enter \"n\".\n"
+       "If you do not trust this host, press Return to abandon the\n"
+       "connection.\n"
+       "Store key in cache? (y/n) ";
 
     static const char wrongmsg[] =
        "WARNING - POTENTIAL SECURITY BREACH!\n"
 
     static const char wrongmsg[] =
        "WARNING - POTENTIAL SECURITY BREACH!\n"
@@ -116,9 +131,9 @@ void verify_ssh_host_key(char *host, int port, char *keytype,
        "The new key fingerprint is:\n"
        "%s\n"
        "If you were expecting this change and trust the new key,\n"
        "The new key fingerprint is:\n"
        "%s\n"
        "If you were expecting this change and trust the new key,\n"
-       "enter Yes to update PuTTY's cache and continue connecting.\n"
+       "enter \"y\" to update PuTTY's cache and continue connecting.\n"
        "If you want to carry on connecting but without updating\n"
        "If you want to carry on connecting but without updating\n"
-       "the cache, enter No.\n"
+       "the cache, enter \"n\".\n"
        "If you want to abandon the connection completely, press\n"
        "Return to cancel. Pressing Return is the ONLY guaranteed\n"
        "safe choice.\n"
        "If you want to abandon the connection completely, press\n"
        "Return to cancel. Pressing Return is the ONLY guaranteed\n"
        "safe choice.\n"
@@ -135,28 +150,69 @@ void verify_ssh_host_key(char *host, int port, char *keytype,
 
     if (ret == 0)                     /* success - key matched OK */
        return;
 
     if (ret == 0)                     /* success - key matched OK */
        return;
+
     if (ret == 2) {                   /* key was different */
        fprintf(stderr, wrongmsg, fingerprint);
        fflush(stderr);
     if (ret == 2) {                   /* key was different */
        fprintf(stderr, wrongmsg, fingerprint);
        fflush(stderr);
-       if (fgets(line, sizeof(line), stdin) &&
-           line[0] != '\0' && line[0] != '\n') {
-           if (line[0] == 'y' || line[0] == 'Y')
-               store_host_key(host, port, keytype, keystr);
-       } else {
-           fprintf(stderr, abandoned);
-           fflush(stderr);
-           exit(0);
-       }
     }
     if (ret == 1) {                   /* key was absent */
        fprintf(stderr, absentmsg, fingerprint);
     }
     if (ret == 1) {                   /* key was absent */
        fprintf(stderr, absentmsg, fingerprint);
-       if (fgets(line, sizeof(line), stdin) &&
-           (line[0] == 'y' || line[0] == 'Y'))
+       fflush(stderr);
+    }
+
+    hin = GetStdHandle(STD_INPUT_HANDLE);
+    GetConsoleMode(hin, &savemode);
+    SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+                        ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+    ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+    SetConsoleMode(hin, savemode);
+
+    if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
+       if (line[0] == 'y' || line[0] == 'Y')
            store_host_key(host, port, keytype, keystr);
            store_host_key(host, port, keytype, keystr);
-       else {
-           fprintf(stderr, abandoned);
-           exit(0);
-       }
+    } else {
+       fprintf(stderr, abandoned);
+       exit(0);
+    }
+}
+
+/*
+ * Ask whether the selected cipher is acceptable (since it was
+ * below the configured 'warn' threshold).
+ * cs: 0 = both ways, 1 = client->server, 2 = server->client
+ */
+void askcipher(char *ciphername, int cs)
+{
+    HANDLE hin;
+    DWORD savemode, i;
+
+    static const char msg[] =
+       "The first %scipher supported by the server is\n"
+       "%s, which is below the configured warning threshold.\n"
+       "Continue with connection? (y/n) ";
+    static const char abandoned[] = "Connection abandoned.\n";
+
+    char line[32];
+
+    fprintf(stderr, msg,
+           (cs == 0) ? "" :
+           (cs == 1) ? "client-to-server " :
+                       "server-to-client ",
+           ciphername);
+    fflush(stderr);
+
+    hin = GetStdHandle(STD_INPUT_HANDLE);
+    GetConsoleMode(hin, &savemode);
+    SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+                        ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+    ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+    SetConsoleMode(hin, savemode);
+
+    if (line[0] == 'y' || line[0] == 'Y') {
+       return;
+    } else {
+       fprintf(stderr, abandoned);
+       exit(0);
     }
 }
 
     }
 }
 
@@ -234,11 +290,21 @@ void fatalbox(char *fmt, ...)
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
-    strcpy(str, "Fatal:");
+    strcpy(str, "Fatal: ");
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
+    errs++;
+
+    if (gui_mode) {
+       unsigned int msg_id = WM_RET_ERR_CNT;
+       if (list)
+           msg_id = WM_LS_RET_ERR_CNT;
+       while (!PostMessage
+              ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
+               0 /*lParam */ ))SleepEx(1000, TRUE);
+    }
 
     exit(1);
 }
 
     exit(1);
 }
@@ -247,11 +313,21 @@ void connection_fatal(char *fmt, ...)
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
-    strcpy(str, "Fatal:");
+    strcpy(str, "Fatal: ");
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
+    errs++;
+
+    if (gui_mode) {
+       unsigned int msg_id = WM_RET_ERR_CNT;
+       if (list)
+           msg_id = WM_LS_RET_ERR_CNT;
+       while (!PostMessage
+              ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
+               0 /*lParam */ ))SleepEx(1000, TRUE);
+    }
 
     exit(1);
 }
 
     exit(1);
 }
@@ -283,7 +359,7 @@ static unsigned char *outptr;              /* where to put the data */
 static unsigned outlen;                       /* how much data required */
 static unsigned char *pending = NULL;  /* any spare data */
 static unsigned pendlen = 0, pendsize = 0;     /* length and phys. size of buffer */
 static unsigned outlen;                       /* how much data required */
 static unsigned char *pending = NULL;  /* any spare data */
 static unsigned pendlen = 0, pendsize = 0;     /* length and phys. size of buffer */
-void from_backend(int is_stderr, char *data, int datalen)
+int from_backend(int is_stderr, char *data, int datalen)
 {
     unsigned char *p = (unsigned char *) data;
     unsigned len = (unsigned) datalen;
 {
     unsigned char *p = (unsigned char *) data;
     unsigned len = (unsigned) datalen;
@@ -294,7 +370,7 @@ void from_backend(int is_stderr, char *data, int datalen)
      */
     if (is_stderr) {
        fwrite(data, 1, len, stderr);
      */
     if (is_stderr) {
        fwrite(data, 1, len, stderr);
-       return;
+       return 0;
     }
 
     inbuf_head = 0;
     }
 
     inbuf_head = 0;
@@ -303,7 +379,7 @@ void from_backend(int is_stderr, char *data, int datalen)
      * If this is before the real session begins, just return.
      */
     if (!outptr)
      * If this is before the real session begins, just return.
      */
     if (!outptr)
-       return;
+       return 0;
 
     if (outlen > 0) {
        unsigned used = outlen;
 
     if (outlen > 0) {
        unsigned used = outlen;
@@ -327,6 +403,19 @@ void from_backend(int is_stderr, char *data, int datalen)
        memcpy(pending + pendlen, p, len);
        pendlen += len;
     }
        memcpy(pending + pendlen, p, len);
        pendlen += len;
     }
+
+    return 0;
+}
+static int scp_process_network_event(void)
+{
+    fd_set readfds;
+
+    FD_ZERO(&readfds);
+    FD_SET(scp_ssh_socket, &readfds);
+    if (select(1, &readfds, NULL, NULL, NULL) < 0)
+       return 0;                      /* doom */
+    select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
+    return 1;
 }
 static int ssh_scp_recv(unsigned char *buf, int len)
 {
 }
 static int ssh_scp_recv(unsigned char *buf, int len)
 {
@@ -356,13 +445,8 @@ static int ssh_scp_recv(unsigned char *buf, int len)
     }
 
     while (outlen > 0) {
     }
 
     while (outlen > 0) {
-       fd_set readfds;
-
-       FD_ZERO(&readfds);
-       FD_SET(scp_ssh_socket, &readfds);
-       if (select(1, &readfds, NULL, NULL, NULL) < 0)
+       if (!scp_process_network_event())
            return 0;                  /* doom */
            return 0;                  /* doom */
-       select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
     }
 
     return len;
     }
 
     return len;
@@ -383,6 +467,7 @@ static void ssh_scp_init(void)
            return;                    /* doom */
        select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
     }
            return;                    /* doom */
        select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
     }
+    using_sftp = !ssh_fallback_cmd;
 }
 
 /*
 }
 
 /*
@@ -393,17 +478,28 @@ static void bump(char *fmt, ...)
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
     char str[0x100];                  /* Make the size big enough */
     va_list ap;
     va_start(ap, fmt);
-    strcpy(str, "Fatal:");
+    strcpy(str, "Fatal: ");
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
     vsprintf(str + strlen(str), fmt, ap);
     va_end(ap);
     strcat(str, "\n");
     tell_str(stderr, str);
+    errs++;
 
     if (back != NULL && back->socket() != NULL) {
        char ch;
        back->special(TS_EOF);
        ssh_scp_recv(&ch, 1);
     }
 
     if (back != NULL && back->socket() != NULL) {
        char ch;
        back->special(TS_EOF);
        ssh_scp_recv(&ch, 1);
     }
+
+    if (gui_mode) {
+       unsigned int msg_id = WM_RET_ERR_CNT;
+       if (list)
+           msg_id = WM_LS_RET_ERR_CNT;
+       while (!PostMessage
+              ((HWND) atoi(gui_hwnd), msg_id, (WPARAM) errs,
+               0 /*lParam */ ))SleepEx(1000, TRUE);
+    }
+
     exit(1);
 }
 
     exit(1);
 }
 
@@ -505,8 +601,14 @@ static void do_cmd(char *host, char *user, char *cmd)
     if (portnumber)
        cfg.port = portnumber;
 
     if (portnumber)
        cfg.port = portnumber;
 
-    strncpy(cfg.remote_cmd, cmd, sizeof(cfg.remote_cmd));
-    cfg.remote_cmd[sizeof(cfg.remote_cmd) - 1] = '\0';
+    /*
+     * Attempt to start the SFTP subsystem as a first choice,
+     * falling back to the provided scp command if that fails.
+     */
+    strcpy(cfg.remote_cmd, "sftp");
+    cfg.ssh_subsys = TRUE;
+    cfg.remote_cmd_ptr2 = cmd;
+    cfg.ssh_subsys2 = FALSE;
     cfg.nopty = TRUE;
 
     back = &ssh_backend;
     cfg.nopty = TRUE;
 
     back = &ssh_backend;
@@ -530,6 +632,7 @@ static void print_stats(char *name, unsigned long size, unsigned long done,
     unsigned long eta;
     char etastr[10];
     int pct;
     unsigned long eta;
     char etastr[10];
     int pct;
+    int len;
 
     /* GUI Adaptation - Sept 2000 */
     if (gui_mode)
 
     /* GUI Adaptation - Sept 2000 */
     if (gui_mode)
@@ -550,8 +653,11 @@ static void print_stats(char *name, unsigned long size, unsigned long done,
 
        pct = (int) (100.0 * (float) done / size);
 
 
        pct = (int) (100.0 * (float) done / size);
 
-       printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
-              name, done / 1024, ratebs / 1024.0, etastr, pct);
+       len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
+                    name, done / 1024, ratebs / 1024.0, etastr, pct);
+       if (len < prev_stats_len)
+           printf("%*s", prev_stats_len - len, "");
+       prev_stats_len = len;
 
        if (done == size)
            printf("\n");
 
        if (done == size)
            printf("\n");
@@ -578,6 +684,31 @@ static char *colon(char *str)
 }
 
 /*
 }
 
 /*
+ * Return a pointer to the portion of str that comes after the last
+ * slash or backslash.
+ */
+static char *stripslashes(char *str)
+{
+    char *p;
+
+    p = strrchr(str, '/');
+    if (p) str = p+1;
+
+    p = strrchr(str, '\\');
+    if (p) str = p+1;
+
+    return str;
+}
+
+/*
+ * Determine whether a string is entirely composed of dots.
+ */
+static int is_dots(char *str)
+{
+    return str[strspn(str, ".")] == '\0';
+}
+
+/*
  *  Wait for a response from the other side.
  *  Return 0 if ok, -1 if error.
  */
  *  Wait for a response from the other side.
  *  Return 0 if ok, -1 if error.
  */
@@ -613,7 +744,667 @@ static int response(void)
     }
 }
 
     }
 }
 
+int sftp_recvdata(char *buf, int len)
+{
+    return ssh_scp_recv(buf, len);
+}
+int sftp_senddata(char *buf, int len)
+{
+    back->send((unsigned char *) buf, len);
+    return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * sftp-based replacement for the hacky `pscp -ls'.
+ */
+static int sftp_ls_compare(const void *av, const void *bv)
+{
+    const struct fxp_name *a = (const struct fxp_name *) av;
+    const struct fxp_name *b = (const struct fxp_name *) bv;
+    return strcmp(a->filename, b->filename);
+}
+void scp_sftp_listdir(char *dirname)
+{
+    struct fxp_handle *dirh;
+    struct fxp_names *names;
+    struct fxp_name *ournames;
+    int nnames, namesize;
+    char *dir;
+    int i;
+
+    printf("Listing directory %s\n", dirname);
+
+    dirh = fxp_opendir(dirname);
+    if (dirh == NULL) {
+       printf("Unable to open %s: %s\n", dir, fxp_error());
+    } else {
+       nnames = namesize = 0;
+       ournames = NULL;
+
+       while (1) {
+
+           names = fxp_readdir(dirh);
+           if (names == NULL) {
+               if (fxp_error_type() == SSH_FX_EOF)
+                   break;
+               printf("Reading directory %s: %s\n", dir, fxp_error());
+               break;
+           }
+           if (names->nnames == 0) {
+               fxp_free_names(names);
+               break;
+           }
+
+           if (nnames + names->nnames >= namesize) {
+               namesize += names->nnames + 128;
+               ournames =
+                   srealloc(ournames, namesize * sizeof(*ournames));
+           }
+
+           for (i = 0; i < names->nnames; i++)
+               ournames[nnames++] = names->names[i];
+
+           names->nnames = 0;         /* prevent free_names */
+           fxp_free_names(names);
+       }
+       fxp_close(dirh);
+
+       /*
+        * Now we have our filenames. Sort them by actual file
+        * name, and then output the longname parts.
+        */
+       qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
+
+       /*
+        * And print them.
+        */
+       for (i = 0; i < nnames; i++)
+           printf("%s\n", ournames[i].longname);
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Helper routines that contain the actual SCP protocol elements,
+ * implemented both as SCP1 and SFTP.
+ */
+
+static struct scp_sftp_dirstack {
+    struct scp_sftp_dirstack *next;
+    struct fxp_name *names;
+    int namepos, namelen;
+    char *dirpath;
+} *scp_sftp_dirstack_head;
+static char *scp_sftp_remotepath, *scp_sftp_currentname;
+static int scp_sftp_targetisdir, scp_sftp_donethistarget;
+static int scp_sftp_preserve, scp_sftp_recursive;
+static unsigned long scp_sftp_mtime, scp_sftp_atime;
+static int scp_has_times;
+static struct fxp_handle *scp_sftp_filehandle;
+static uint64 scp_sftp_fileoffset;
+
+void scp_source_setup(char *target, int shouldbedir)
+{
+    if (using_sftp) {
+       /*
+        * Find out whether the target filespec is in fact a
+        * directory.
+        */
+       struct fxp_attrs attrs;
+
+       if (!fxp_stat(target, &attrs) ||
+           !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
+           scp_sftp_targetisdir = 0;
+       else
+           scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0;
+
+       if (shouldbedir && !scp_sftp_targetisdir) {
+           bump("pscp: remote filespec %s: not a directory\n", target);
+       }
+
+       scp_sftp_remotepath = dupstr(target);
+
+       scp_has_times = 0;
+    } else {
+       (void) response();
+    }
+}
+
+int scp_send_errmsg(char *str)
+{
+    if (using_sftp) {
+       /* do nothing; we never need to send our errors to the server */
+    } else {
+       back->send("\001", 1);         /* scp protocol error prefix */
+       back->send(str, strlen(str));
+    }
+    return 0;                         /* can't fail */
+}
+
+int scp_send_filetimes(unsigned long mtime, unsigned long atime)
+{
+    if (using_sftp) {
+       scp_sftp_mtime = mtime;
+       scp_sftp_atime = atime;
+       scp_has_times = 1;
+       return 0;
+    } else {
+       char buf[80];
+       sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
+       back->send(buf, strlen(buf));
+       return response();
+    }
+}
+
+int scp_send_filename(char *name, unsigned long size, int modes)
+{
+    if (using_sftp) {
+       char *fullname;
+       if (scp_sftp_targetisdir) {
+           fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+       } else {
+           fullname = dupstr(scp_sftp_remotepath);
+       }
+       scp_sftp_filehandle =
+           fxp_open(fullname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
+       if (!scp_sftp_filehandle) {
+           tell_user(stderr, "pscp: unable to open %s: %s",
+                     fullname, fxp_error());
+           errs++;
+           return 1;
+       }
+       scp_sftp_fileoffset = uint64_make(0, 0);
+       sfree(fullname);
+       return 0;
+    } else {
+       char buf[40];
+       sprintf(buf, "C%04o %lu ", modes, size);
+       back->send(buf, strlen(buf));
+       back->send(name, strlen(name));
+       back->send("\n", 1);
+       return response();
+    }
+}
+
+int scp_send_filedata(char *data, int len)
+{
+    if (using_sftp) {
+       if (!scp_sftp_filehandle) {
+           return 1;
+       }
+       if (!fxp_write(scp_sftp_filehandle, data, scp_sftp_fileoffset, len)) {
+           tell_user(stderr, "error while writing: %s\n", fxp_error());
+           errs++;
+           return 1;
+       }
+       scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len);
+       return 0;
+    } else {
+       int bufsize = back->send(data, len);
+
+       /*
+        * If the network transfer is backing up - that is, the
+        * remote site is not accepting data as fast as we can
+        * produce it - then we must loop on network events until
+        * we have space in the buffer again.
+        */
+       while (bufsize > MAX_SCP_BUFSIZE) {
+           if (!scp_process_network_event())
+               return 1;
+           bufsize = back->sendbuffer();
+       }
+
+       return 0;
+    }
+}
+
+int scp_send_finish(void)
+{
+    if (using_sftp) {
+       struct fxp_attrs attrs;
+       if (!scp_sftp_filehandle) {
+           return 1;
+       }
+       if (scp_has_times) {
+           attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
+           attrs.atime = scp_sftp_atime;
+           attrs.mtime = scp_sftp_mtime;
+           if (!fxp_fsetstat(scp_sftp_filehandle, attrs)) {
+               tell_user(stderr, "unable to set file times: %s\n", fxp_error());
+               errs++;
+           }
+       }
+       fxp_close(scp_sftp_filehandle);
+       scp_has_times = 0;
+       return 0;
+    } else {
+       back->send("", 1);
+       return response();
+    }
+}
+
+char *scp_save_remotepath(void)
+{
+    if (using_sftp)
+       return scp_sftp_remotepath;
+    else
+       return NULL;
+}
+
+void scp_restore_remotepath(char *data)
+{
+    if (using_sftp)
+       scp_sftp_remotepath = data;
+}
+
+int scp_send_dirname(char *name, int modes)
+{
+    if (using_sftp) {
+       char *fullname;
+       char const *err;
+       struct fxp_attrs attrs;
+       if (scp_sftp_targetisdir) {
+           fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+       } else {
+           fullname = dupstr(scp_sftp_remotepath);
+       }
+
+       /*
+        * We don't worry about whether we managed to create the
+        * directory, because if it exists already it's OK just to
+        * use it. Instead, we will stat it afterwards, and if it
+        * exists and is a directory we will assume we were either
+        * successful or it didn't matter.
+        */
+       if (!fxp_mkdir(fullname))
+           err = fxp_error();
+       else
+           err = "server reported no error";
+       if (!fxp_stat(fullname, &attrs) ||
+           !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
+           !(attrs.permissions & 0040000)) {
+           tell_user(stderr, "unable to create directory %s: %s",
+                     fullname, err);
+           errs++;
+           return 1;
+       }
+
+       scp_sftp_remotepath = fullname;
+
+       return 0;
+    } else {
+       char buf[40];
+       sprintf(buf, "D%04o 0 ", modes);
+       back->send(buf, strlen(buf));
+       back->send(name, strlen(name));
+       back->send("\n", 1);
+       return response();
+    }
+}
+
+int scp_send_enddir(void)
+{
+    if (using_sftp) {
+       sfree(scp_sftp_remotepath);
+       return 0;
+    } else {
+       back->send("E\n", 2);
+       return response();
+    }
+}
+
 /*
 /*
+ * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init.
+ * That's bad. The difference is that scp_sink_setup is called once
+ * right at the start, whereas scp_sink_init is called to
+ * initialise every level of recursion in the protocol.
+ */
+void scp_sink_setup(char *source, int preserve, int recursive)
+{
+    if (using_sftp) {
+       scp_sftp_remotepath = dupstr(source);
+       scp_sftp_preserve = preserve;
+       scp_sftp_recursive = recursive;
+       scp_sftp_donethistarget = 0;
+       scp_sftp_dirstack_head = NULL;
+    }
+}
+
+int scp_sink_init(void)
+{
+    if (!using_sftp) {
+       back->send("", 1);
+    }
+    return 0;
+}
+
+#define SCP_SINK_FILE   1
+#define SCP_SINK_DIR    2
+#define SCP_SINK_ENDDIR 3
+struct scp_sink_action {
+    int action;                               /* FILE, DIR, ENDDIR */
+    char *buf;                        /* will need freeing after use */
+    char *name;                               /* filename or dirname (not ENDDIR) */
+    int mode;                         /* access mode (not ENDDIR) */
+    unsigned long size;                       /* file size (not ENDDIR) */
+    int settime;                      /* 1 if atime and mtime are filled */
+    unsigned long atime, mtime;               /* access times for the file */
+};
+
+int scp_get_sink_action(struct scp_sink_action *act)
+{
+    if (using_sftp) {
+       char *fname;
+       int must_free_fname;
+       struct fxp_attrs attrs;
+       int ret;
+
+       if (!scp_sftp_dirstack_head) {
+           if (!scp_sftp_donethistarget) {
+               /*
+                * Simple case: we are only dealing with one file.
+                */
+               fname = scp_sftp_remotepath;
+               must_free_fname = 0;
+               scp_sftp_donethistarget = 1;
+           } else {
+               /*
+                * Even simpler case: one file _which we've done_.
+                * Return 1 (finished).
+                */
+               return 1;
+           }
+       } else {
+           /*
+            * We're now in the middle of stepping through a list
+            * of names returned from fxp_readdir(); so let's carry
+            * on.
+            */
+           struct scp_sftp_dirstack *head = scp_sftp_dirstack_head;
+           while (head->namepos < head->namelen &&
+                  is_dots(head->names[head->namepos].filename))
+               head->namepos++;       /* skip . and .. */
+           if (head->namepos < head->namelen) {
+               fname = dupcat(head->dirpath, "/",
+                              head->names[head->namepos++].filename,
+                              NULL);
+               must_free_fname = 1;
+           } else {
+               /*
+                * We've come to the end of the list; pop it off
+                * the stack and return an ENDDIR action.
+                */
+               
+               sfree(head->dirpath);
+               sfree(head->names);
+               scp_sftp_dirstack_head = head->next;
+               sfree(head);
+
+               act->action = SCP_SINK_ENDDIR;
+               return 0;
+           }
+       }
+
+       /*
+        * Now we have a filename. Stat it, and see if it's a file
+        * or a directory.
+        */
+       ret = fxp_stat(fname, &attrs);
+       if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+           tell_user(stderr, "unable to identify %s: %s", fname,
+                     ret ? "file type not supplied" : fxp_error());
+           errs++;
+           return 1;
+       }
+
+       if (attrs.permissions & 0040000) {
+           struct scp_sftp_dirstack *newitem;
+           struct fxp_handle *dirhandle;
+           int nnames, namesize;
+           struct fxp_name *ournames;
+           struct fxp_names *names;
+
+           /*
+            * It's a directory. If we're not in recursive
+            * mode, this just merits a complaint.
+            */
+           if (!scp_sftp_recursive) {
+               tell_user(stderr, "pscp: %s: is a directory", fname);
+               errs++;
+               if (must_free_fname) sfree(fname);
+               return 1;
+           }
+
+           /*
+            * Otherwise, the fun begins. We must fxp_opendir() the
+            * directory, slurp the filenames into memory, return
+            * SCP_SINK_DIR, and set targetisdir. The next time
+            * we're called, we will run through the list of
+            * filenames one by one.
+            * 
+            * If targetisdir is _already_ set (meaning we're
+            * already in the middle of going through another such
+            * list), we must push the other (target,namelist) pair
+            * on a stack.
+            */
+           dirhandle = fxp_opendir(fname);
+           if (!dirhandle) {
+               tell_user(stderr, "scp: unable to open directory %s: %s",
+                         fname, fxp_error());
+               if (must_free_fname) sfree(fname);
+               errs++;
+               return 1;
+           }
+           nnames = namesize = 0;
+           ournames = NULL;
+           while (1) {
+               int i;
+
+               names = fxp_readdir(dirhandle);
+               if (names == NULL) {
+                   if (fxp_error_type() == SSH_FX_EOF)
+                       break;
+                   tell_user(stderr, "scp: reading directory %s: %s\n",
+                             fname, fxp_error());
+                   if (must_free_fname) sfree(fname);
+                   sfree(ournames);
+                   errs++;
+                   return 1;
+               }
+               if (names->nnames == 0) {
+                   fxp_free_names(names);
+                   break;
+               }
+               if (nnames + names->nnames >= namesize) {
+                   namesize += names->nnames + 128;
+                   ournames =
+                       srealloc(ournames, namesize * sizeof(*ournames));
+               }
+               for (i = 0; i < names->nnames; i++)
+                   ournames[nnames++] = names->names[i];
+               names->nnames = 0;             /* prevent free_names */
+               fxp_free_names(names);
+           }
+           fxp_close(dirhandle);
+
+           newitem = smalloc(sizeof(struct scp_sftp_dirstack));
+           newitem->next = scp_sftp_dirstack_head;
+           newitem->names = ournames;
+           newitem->namepos = 0;
+           newitem->namelen = nnames;
+           if (must_free_fname)
+               newitem->dirpath = fname;
+           else
+               newitem->dirpath = dupstr(fname);
+           scp_sftp_dirstack_head = newitem;
+
+           act->action = SCP_SINK_DIR;
+           act->buf = dupstr(stripslashes(fname));
+           act->name = act->buf;
+           act->size = 0;             /* duhh, it's a directory */
+           act->mode = 07777 & attrs.permissions;
+           if (scp_sftp_preserve &&
+               (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+               act->atime = attrs.atime;
+               act->mtime = attrs.mtime;
+               act->settime = 1;
+           } else
+               act->settime = 0;
+           return 0;
+
+       } else {
+           /*
+            * It's a file. Return SCP_SINK_FILE.
+            */
+           act->action = SCP_SINK_FILE;
+           act->buf = dupstr(stripslashes(fname));
+           act->name = act->buf;
+           if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
+               if (uint64_compare(attrs.size,
+                                  uint64_make(0, ULONG_MAX)) > 0) {
+                   act->size = ULONG_MAX;   /* *boggle* */
+               } else
+                   act->size = attrs.size.lo;
+           } else
+               act->size = ULONG_MAX;   /* no idea */
+           act->mode = 07777 & attrs.permissions;
+           if (scp_sftp_preserve &&
+               (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+               act->atime = attrs.atime;
+               act->mtime = attrs.mtime;
+               act->settime = 1;
+           } else
+               act->settime = 0;
+           if (must_free_fname)
+               scp_sftp_currentname = fname;
+           else
+               scp_sftp_currentname = dupstr(fname);
+           return 0;
+       }
+
+    } else {
+       int done = 0;
+       int i, bufsize;
+       int action;
+       char ch;
+
+       act->settime = 0;
+       act->buf = NULL;
+       bufsize = 0;
+
+       while (!done) {
+           if (ssh_scp_recv(&ch, 1) <= 0)
+               return 1;
+           if (ch == '\n')
+               bump("Protocol error: Unexpected newline");
+           i = 0;
+           action = ch;
+           do {
+               if (ssh_scp_recv(&ch, 1) <= 0)
+                   bump("Lost connection");
+               if (i >= bufsize) {
+                   bufsize = i + 128;
+                   act->buf = srealloc(act->buf, bufsize);
+               }
+               act->buf[i++] = ch;
+           } while (ch != '\n');
+           act->buf[i - 1] = '\0';
+           switch (action) {
+             case '\01':                      /* error */
+               tell_user(stderr, "%s\n", act->buf);
+               errs++;
+               continue;                      /* go round again */
+             case '\02':                      /* fatal error */
+               bump("%s", act->buf);
+             case 'E':
+               back->send("", 1);
+               act->action = SCP_SINK_ENDDIR;
+               return 0;
+             case 'T':
+               if (sscanf(act->buf, "%ld %*d %ld %*d",
+                          &act->mtime, &act->atime) == 2) {
+                   act->settime = 1;
+                   back->send("", 1);
+                   continue;          /* go round again */
+               }
+               bump("Protocol error: Illegal time format");
+             case 'C':
+             case 'D':
+               act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR);
+               break;
+             default:
+               bump("Protocol error: Expected control record");
+           }
+           /*
+            * We will go round this loop only once, unless we hit
+            * `continue' above.
+            */
+           done = 1;
+       }
+
+       /*
+        * If we get here, we must have seen SCP_SINK_FILE or
+        * SCP_SINK_DIR.
+        */
+       if (sscanf(act->buf, "%o %lu %n", &act->mode, &act->size, &i) != 2)
+           bump("Protocol error: Illegal file descriptor format");
+       act->name = act->buf + i;
+       return 0;
+    }
+}
+
+int scp_accept_filexfer(void)
+{
+    if (using_sftp) {
+       scp_sftp_filehandle =
+           fxp_open(scp_sftp_currentname, SSH_FXF_READ);
+       if (!scp_sftp_filehandle) {
+           tell_user(stderr, "pscp: unable to open %s: %s",
+                     scp_sftp_currentname, fxp_error());
+           errs++;
+           return 1;
+       }
+       scp_sftp_fileoffset = uint64_make(0, 0);
+       sfree(scp_sftp_currentname);
+       return 0;
+    } else {
+       back->send("", 1);
+       return 0;                      /* can't fail */
+    }
+}
+
+int scp_recv_filedata(char *data, int len)
+{
+    if (using_sftp) {
+       int actuallen = fxp_read(scp_sftp_filehandle, data,
+                                scp_sftp_fileoffset, len);
+       if (actuallen == -1 && fxp_error_type() != SSH_FX_EOF) {
+           tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+           errs++;
+           return -1;
+       }
+       if (actuallen < 0)
+           actuallen = 0;
+
+       scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen);
+
+       return actuallen;
+    } else {
+       return ssh_scp_recv(data, len);
+    }
+}
+
+int scp_finish_filerecv(void)
+{
+    if (using_sftp) {
+       fxp_close(scp_sftp_filehandle);
+       return 0;
+    } else {
+       back->send("", 1);
+       return response();
+    }
+}
+
+/* ----------------------------------------------------------------------
  *  Send an error message to the other side and to the screen.
  *  Increment error counter.
  */
  *  Send an error message to the other side and to the screen.
  *  Increment error counter.
  */
@@ -626,8 +1417,7 @@ static void run_err(const char *fmt, ...)
     strcpy(str, "scp: ");
     vsprintf(str + strlen(str), fmt, ap);
     strcat(str, "\n");
     strcpy(str, "scp: ");
     vsprintf(str + strlen(str), fmt, ap);
     strcat(str, "\n");
-    back->send("\001", 1);            /* scp protocol error prefix */
-    back->send(str, strlen(str));
+    scp_send_errmsg(str);
     tell_user(stderr, "%s", str);
     va_end(ap);
 }
     tell_user(stderr, "%s", str);
     va_end(ap);
 }
@@ -637,7 +1427,6 @@ static void run_err(const char *fmt, ...)
  */
 static void source(char *src)
 {
  */
 static void source(char *src)
 {
-    char buf[2048];
     unsigned long size;
     char *last;
     HANDLE f;
     unsigned long size;
     char *last;
     HANDLE f;
@@ -697,29 +1486,24 @@ static void source(char *src)
        GetFileTime(f, NULL, &actime, &wrtime);
        TIME_WIN_TO_POSIX(actime, atime);
        TIME_WIN_TO_POSIX(wrtime, mtime);
        GetFileTime(f, NULL, &actime, &wrtime);
        TIME_WIN_TO_POSIX(actime, atime);
        TIME_WIN_TO_POSIX(wrtime, mtime);
-       sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
-       back->send(buf, strlen(buf));
-       if (response())
+       if (scp_send_filetimes(mtime, atime))
            return;
     }
 
     size = GetFileSize(f, NULL);
            return;
     }
 
     size = GetFileSize(f, NULL);
-    sprintf(buf, "C0644 %lu %s\n", size, last);
     if (verbose)
     if (verbose)
-       tell_user(stderr, "Sending file modes: %s", buf);
-    back->send(buf, strlen(buf));
-    if (response())
+       tell_user(stderr, "Sending file %s, size=%lu", last, size);
+    if (scp_send_filename(last, size, 0644))
        return;
 
        return;
 
-    if (statistics) {
-       stat_bytes = 0;
-       stat_starttime = time(NULL);
-       stat_lasttime = 0;
-    }
+    stat_bytes = 0;
+    stat_starttime = time(NULL);
+    stat_lasttime = 0;
 
     for (i = 0; i < size; i += 4096) {
        char transbuf[4096];
        DWORD j, k = 4096;
 
     for (i = 0; i < size; i += 4096) {
        char transbuf[4096];
        DWORD j, k = 4096;
+
        if (i + k > size)
            k = size - i;
        if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
        if (i + k > size)
            k = size - i;
        if (!ReadFile(f, transbuf, k, &j, NULL) || j != k) {
@@ -727,7 +1511,9 @@ static void source(char *src)
                printf("\n");
            bump("%s: Read error", src);
        }
                printf("\n");
            bump("%s: Read error", src);
        }
-       back->send(transbuf, k);
+       if (scp_send_filedata(transbuf, k))
+           bump("%s: Network error occurred", src);
+
        if (statistics) {
            stat_bytes += k;
            if (time(NULL) != stat_lasttime || i + k == size) {
        if (statistics) {
            stat_bytes += k;
            if (time(NULL) != stat_lasttime || i + k == size) {
@@ -736,11 +1522,11 @@ static void source(char *src)
                            stat_starttime, stat_lasttime);
            }
        }
                            stat_starttime, stat_lasttime);
            }
        }
+
     }
     CloseHandle(f);
 
     }
     CloseHandle(f);
 
-    back->send("", 1);
-    (void) response();
+    (void) scp_send_finish();
 }
 
 /*
 }
 
 /*
@@ -748,8 +1534,8 @@ static void source(char *src)
  */
 static void rsource(char *src)
 {
  */
 static void rsource(char *src)
 {
-    char buf[2048];
-    char *last;
+    char *last, *findfile;
+    char *save_target;
     HANDLE dir;
     WIN32_FIND_DATA fdat;
     int ok;
     HANDLE dir;
     WIN32_FIND_DATA fdat;
     int ok;
@@ -765,50 +1551,48 @@ static void rsource(char *src)
 
     /* maybe send filetime */
 
 
     /* maybe send filetime */
 
-    sprintf(buf, "D0755 0 %s\n", last);
+    save_target = scp_save_remotepath();
+
     if (verbose)
     if (verbose)
-       tell_user(stderr, "Entering directory: %s", buf);
-    back->send(buf, strlen(buf));
-    if (response())
+       tell_user(stderr, "Entering directory: %s", last);
+    if (scp_send_dirname(last, 0755))
        return;
 
        return;
 
-    sprintf(buf, "%s/*", src);
-    dir = FindFirstFile(buf, &fdat);
+    findfile = dupcat(src, "/*", NULL);
+    dir = FindFirstFile(findfile, &fdat);
     ok = (dir != INVALID_HANDLE_VALUE);
     while (ok) {
        if (strcmp(fdat.cFileName, ".") == 0 ||
            strcmp(fdat.cFileName, "..") == 0) {
     ok = (dir != INVALID_HANDLE_VALUE);
     while (ok) {
        if (strcmp(fdat.cFileName, ".") == 0 ||
            strcmp(fdat.cFileName, "..") == 0) {
-       } else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) {
-           run_err("%s/%s: Name too long", src, fdat.cFileName);
+           /* ignore . and .. */
        } else {
        } else {
-           sprintf(buf, "%s/%s", src, fdat.cFileName);
-           source(buf);
+           char *foundfile = dupcat(src, "/", fdat.cFileName, NULL);
+           source(foundfile);
+           sfree(foundfile);
        }
        ok = FindNextFile(dir, &fdat);
     }
     FindClose(dir);
        }
        ok = FindNextFile(dir, &fdat);
     }
     FindClose(dir);
+    sfree(findfile);
 
 
-    sprintf(buf, "E\n");
-    back->send(buf, strlen(buf));
-    (void) response();
+    (void) scp_send_enddir();
+
+    scp_restore_remotepath(save_target);
 }
 
 /*
 }
 
 /*
- *  Execute the sink part of the SCP protocol.
+ * Execute the sink part of the SCP protocol.
  */
 static void sink(char *targ, char *src)
 {
  */
 static void sink(char *targ, char *src)
 {
-    char buf[2048];
-    char namebuf[2048];
+    char *destfname;
     char ch;
     int targisdir = 0;
     int settime;
     int exists;
     DWORD attr;
     HANDLE f;
     char ch;
     int targisdir = 0;
     int settime;
     int exists;
     DWORD attr;
     HANDLE f;
-    unsigned long mtime, atime;
-    unsigned int mode;
-    unsigned long size, i;
+    unsigned long received;
     int wrerror = 0;
     unsigned long stat_bytes;
     time_t stat_starttime, stat_lasttime;
     int wrerror = 0;
     unsigned long stat_bytes;
     time_t stat_starttime, stat_lasttime;
@@ -821,114 +1605,134 @@ static void sink(char *targ, char *src)
     if (targetshouldbedirectory && !targisdir)
        bump("%s: Not a directory", targ);
 
     if (targetshouldbedirectory && !targisdir)
        bump("%s: Not a directory", targ);
 
-    back->send("", 1);
+    scp_sink_init();
     while (1) {
     while (1) {
-       settime = 0;
-      gottime:
-       if (ssh_scp_recv(&ch, 1) <= 0)
+       struct scp_sink_action act;
+       if (scp_get_sink_action(&act))
            return;
            return;
-       if (ch == '\n')
-           bump("Protocol error: Unexpected newline");
-       i = 0;
-       buf[i++] = ch;
-       do {
-           if (ssh_scp_recv(&ch, 1) <= 0)
-               bump("Lost connection");
-           buf[i++] = ch;
-       } while (i < sizeof(buf) && ch != '\n');
-       buf[i - 1] = '\0';
-       switch (buf[0]) {
-         case '\01':                  /* error */
-           tell_user(stderr, "%s\n", buf + 1);
-           errs++;
-           continue;
-         case '\02':                  /* fatal error */
-           bump("%s", buf + 1);
-         case 'E':
-           back->send("", 1);
+
+       if (act.action == SCP_SINK_ENDDIR)
            return;
            return;
-         case 'T':
-           if (sscanf(buf, "T%ld %*d %ld %*d", &mtime, &atime) == 2) {
-               settime = 1;
-               back->send("", 1);
-               goto gottime;
-           }
-           bump("Protocol error: Illegal time format");
-         case 'C':
-         case 'D':
-           break;
-         default:
-           bump("Protocol error: Expected control record");
-       }
 
 
-       if (sscanf(buf + 1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
-           bump("Protocol error: Illegal file descriptor format");
-       /* Security fix: ensure the file ends up where we asked for it. */
        if (targisdir) {
        if (targisdir) {
-           char t[2048];
-           char *p;
-           strcpy(t, targ);
+           /*
+            * Prevent the remote side from maliciously writing to
+            * files outside the target area by sending a filename
+            * containing `../'. In fact, it shouldn't be sending
+            * filenames with any slashes in at all; so we'll find
+            * the last slash or backslash in the filename and use
+            * only the part after that. (And warn!)
+            * 
+            * In addition, we also ensure here that if we're
+            * copying a single file and the target is a directory
+            * (common usage: `pscp host:filename .') the remote
+            * can't send us a _different_ file name. We can
+            * distinguish this case because `src' will be non-NULL
+            * and the last component of that will fail to match
+            * (the last component of) the name sent.
+            * 
+            * (Well, not always; if `src' is a wildcard, we do
+            * expect to get back filenames that don't correspond
+            * exactly to it. So we skip this check if `src'
+            * contains a *, a ? or a []. This is non-ideal - we
+            * would like to ensure that the returned filename
+            * actually matches the wildcard pattern - but one of
+            * SCP's protocol infelicities is that wildcard
+            * matching is done at the server end _by the server's
+            * rules_ and so in general this is infeasible. Live
+            * with it, or upgrade to SFTP.)
+            */
+           char *striptarget, *stripsrc;
+
+           striptarget = stripslashes(act.name);
+           if (striptarget != act.name) {
+               tell_user(stderr, "warning: remote host sent a compound"
+                         " pathname - possibly malicious! (ignored)");
+           }
+
+           /*
+            * Also check to see if the target filename is '.' or
+            * '..', or indeed '...' and so on because Windows
+            * appears to interpret those like '..'.
+            */
+           if (is_dots(striptarget)) {
+               bump("security violation: remote host attempted to write to"
+                    " a '.' or '..' path!");
+           }
+
+           if (src) {
+               stripsrc = stripslashes(src);
+               if (!stripsrc[strcspn(stripsrc, "*?[]")] &&
+                   strcmp(striptarget, stripsrc)) {
+                   tell_user(stderr, "warning: remote host attempted to"
+                             " write to a different filename: disallowing");
+                   /* Override the name the server provided with our own. */
+                   striptarget = stripsrc;
+               }
+           }
+
            if (targ[0] != '\0')
            if (targ[0] != '\0')
-               strcat(t, "/");
-           p = namebuf + strlen(namebuf);
-           while (p > namebuf && p[-1] != '/' && p[-1] != '\\')
-               p--;
-           strcat(t, p);
-           strcpy(namebuf, t);
+               destfname = dupcat(targ, "\\", striptarget, NULL);
+           else
+               destfname = dupstr(striptarget);
        } else {
        } else {
-           strcpy(namebuf, targ);
+           /*
+            * In this branch of the if, the target area is a
+            * single file with an explicitly specified name in any
+            * case, so there's no danger.
+            */
+           destfname = dupstr(targ);
        }
        }
-       attr = GetFileAttributes(namebuf);
+       attr = GetFileAttributes(destfname);
        exists = (attr != (DWORD) - 1);
 
        exists = (attr != (DWORD) - 1);
 
-       if (buf[0] == 'D') {
+       if (act.action == SCP_SINK_DIR) {
            if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
            if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
-               run_err("%s: Not a directory", namebuf);
+               run_err("%s: Not a directory", destfname);
                continue;
            }
            if (!exists) {
                continue;
            }
            if (!exists) {
-               if (!CreateDirectory(namebuf, NULL)) {
-                   run_err("%s: Cannot create directory", namebuf);
+               if (!CreateDirectory(destfname, NULL)) {
+                   run_err("%s: Cannot create directory", destfname);
                    continue;
                }
            }
                    continue;
                }
            }
-           sink(namebuf, NULL);
+           sink(destfname, NULL);
            /* can we set the timestamp for directories ? */
            continue;
        }
 
            /* can we set the timestamp for directories ? */
            continue;
        }
 
-       f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
+       f = CreateFile(destfname, GENERIC_WRITE, 0, NULL,
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (f == INVALID_HANDLE_VALUE) {
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (f == INVALID_HANDLE_VALUE) {
-           run_err("%s: Cannot create file", namebuf);
+           run_err("%s: Cannot create file", destfname);
            continue;
        }
 
            continue;
        }
 
-       back->send("", 1);
+       if (scp_accept_filexfer())
+           return;
 
 
-       if (statistics) {
-           stat_bytes = 0;
-           stat_starttime = time(NULL);
-           stat_lasttime = 0;
-           if ((stat_name = strrchr(namebuf, '/')) == NULL)
-               stat_name = namebuf;
-           else
-               stat_name++;
-           if (strrchr(stat_name, '\\') != NULL)
-               stat_name = strrchr(stat_name, '\\') + 1;
-       }
+       stat_bytes = 0;
+       stat_starttime = time(NULL);
+       stat_lasttime = 0;
+       stat_name = stripslashes(destfname);
 
 
-       for (i = 0; i < size; i += 4096) {
+       received = 0;
+       while (received < act.size) {
            char transbuf[4096];
            char transbuf[4096];
-           DWORD j, k = 4096;
-           if (i + k > size)
-               k = size - i;
-           if (ssh_scp_recv(transbuf, k) == 0)
+           DWORD blksize, read, written;
+           blksize = 4096;
+           if (blksize > act.size - received)
+               blksize = act.size - received;
+           read = scp_recv_filedata(transbuf, blksize);
+           if (read <= 0)
                bump("Lost connection");
            if (wrerror)
                continue;
                bump("Lost connection");
            if (wrerror)
                continue;
-           if (!WriteFile(f, transbuf, k, &j, NULL) || j != k) {
+           if (!WriteFile(f, transbuf, read, &written, NULL) ||
+               written != read) {
                wrerror = 1;
                wrerror = 1;
+               /* FIXME: in sftp we can actually abort the transfer */
                if (statistics)
                    printf("\r%-25.25s | %50s\n",
                           stat_name,
                if (statistics)
                    printf("\r%-25.25s | %50s\n",
                           stat_name,
@@ -936,34 +1740,36 @@ static void sink(char *targ, char *src)
                continue;
            }
            if (statistics) {
                continue;
            }
            if (statistics) {
-               stat_bytes += k;
-               if (time(NULL) > stat_lasttime || i + k == size) {
+               stat_bytes += read;
+               if (time(NULL) > stat_lasttime ||
+                   received + read == act.size) {
                    stat_lasttime = time(NULL);
                    stat_lasttime = time(NULL);
-                   print_stats(stat_name, size, stat_bytes,
+                   print_stats(stat_name, act.size, stat_bytes,
                                stat_starttime, stat_lasttime);
                }
            }
                                stat_starttime, stat_lasttime);
                }
            }
+           received += read;
        }
        }
-       (void) response();
-
-       if (settime) {
+       if (act.settime) {
            FILETIME actime, wrtime;
            FILETIME actime, wrtime;
-           TIME_POSIX_TO_WIN(atime, actime);
-           TIME_POSIX_TO_WIN(mtime, wrtime);
+           TIME_POSIX_TO_WIN(act.atime, actime);
+           TIME_POSIX_TO_WIN(act.mtime, wrtime);
            SetFileTime(f, NULL, &actime, &wrtime);
        }
 
        CloseHandle(f);
        if (wrerror) {
            SetFileTime(f, NULL, &actime, &wrtime);
        }
 
        CloseHandle(f);
        if (wrerror) {
-           run_err("%s: Write error", namebuf);
+           run_err("%s: Write error", destfname);
            continue;
        }
            continue;
        }
-       back->send("", 1);
+       (void) scp_finish_filerecv();
+       sfree(destfname);
+       sfree(act.name);
     }
 }
 
 /*
     }
 }
 
 /*
- *  We will copy local files to a remote server.
+ * We will copy local files to a remote server.
  */
 static void toremote(int argc, char *argv[])
 {
  */
 static void toremote(int argc, char *argv[])
 {
@@ -1019,9 +1825,10 @@ static void toremote(int argc, char *argv[])
     do_cmd(host, user, cmd);
     sfree(cmd);
 
     do_cmd(host, user, cmd);
     sfree(cmd);
 
-    (void) response();
+    scp_source_setup(targ, targetshouldbedirectory);
 
     for (i = 0; i < argc - 1; i++) {
 
     for (i = 0; i < argc - 1; i++) {
+       char *srcpath, *last;
        HANDLE dir;
        WIN32_FIND_DATA fdat;
        src = argv[i];
        HANDLE dir;
        WIN32_FIND_DATA fdat;
        src = argv[i];
@@ -1030,6 +1837,23 @@ static void toremote(int argc, char *argv[])
            errs++;
            continue;
        }
            errs++;
            continue;
        }
+
+       /*
+        * Trim off the last pathname component of `src', to
+        * provide the base pathname which will be prepended to
+        * filenames returned from Find{First,Next}File.
+        */
+       srcpath = dupstr(src);
+       last = stripslashes(srcpath);
+       if (last == srcpath) {
+           last = strchr(srcpath, ':');
+           if (last)
+               last++;
+           else
+               last = srcpath;
+       }
+       *last = '\0';
+
        dir = FindFirstFile(src, &fdat);
        if (dir == INVALID_HANDLE_VALUE) {
            run_err("%s: No such file or directory", src);
        dir = FindFirstFile(src, &fdat);
        if (dir == INVALID_HANDLE_VALUE) {
            run_err("%s: No such file or directory", src);
@@ -1037,7 +1861,7 @@ static void toremote(int argc, char *argv[])
        }
        do {
            char *last;
        }
        do {
            char *last;
-           char namebuf[2048];
+           char *filename;
            /*
             * Ensure that . and .. are never matched by wildcards,
             * but only by deliberate action.
            /*
             * Ensure that . and .. are never matched by wildcards,
             * but only by deliberate action.
@@ -1059,23 +1883,12 @@ static void toremote(int argc, char *argv[])
                } else
                    continue;          /* ignore this one */
            }
                } else
                    continue;          /* ignore this one */
            }
-           if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) {
-               tell_user(stderr, "%s: Name too long", src);
-               continue;
-           }
-           strcpy(namebuf, src);
-           if ((last = strrchr(namebuf, '/')) == NULL)
-               last = namebuf;
-           else
-               last++;
-           if (strrchr(last, '\\') != NULL)
-               last = strrchr(last, '\\') + 1;
-           if (last == namebuf && strrchr(namebuf, ':') != NULL)
-               last = strchr(namebuf, ':') + 1;
-           strcpy(last, fdat.cFileName);
-           source(namebuf);
+           filename = dupcat(srcpath, fdat.cFileName, NULL);
+           source(filename);
+           sfree(filename);
        } while (FindNextFile(dir, &fdat));
        FindClose(dir);
        } while (FindNextFile(dir, &fdat));
        FindClose(dir);
+       sfree(srcpath);
     }
 }
 
     }
 }
 
@@ -1124,6 +1937,8 @@ static void tolocal(int argc, char *argv[])
     do_cmd(host, user, cmd);
     sfree(cmd);
 
     do_cmd(host, user, cmd);
     sfree(cmd);
 
+    scp_sink_setup(src, preserve, recursive);
+
     sink(targ, src);
 }
 
     sink(targ, src);
 }
 
@@ -1179,8 +1994,12 @@ static void get_dir_list(int argc, char *argv[])
     do_cmd(host, user, cmd);
     sfree(cmd);
 
     do_cmd(host, user, cmd);
     sfree(cmd);
 
-    while (ssh_scp_recv(&c, 1) > 0)
-       tell_char(stdout, c);
+    if (using_sftp) {
+       scp_sftp_listdir(src);
+    } else {
+       while (ssh_scp_recv(&c, 1) > 0)
+           tell_char(stdout, c);
+    }
 }
 
 /*
 }
 
 /*
@@ -1236,7 +2055,6 @@ static void usage(void)
 int main(int argc, char *argv[])
 {
     int i;
 int main(int argc, char *argv[])
 {
     int i;
-    int list = 0;
 
     default_protocol = PROT_TELNET;
 
 
     default_protocol = PROT_TELNET;