Sebastian Kuschel reports that pfd_closing can be called for a socket
[u/mdw/putty] / pscp.c
diff --git a/pscp.c b/pscp.c
index 833e46c..3209932 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -41,6 +41,7 @@ static int try_sftp = 1;
 static int main_cmd_is_sftp = 0;
 static int fallback_cmd_is_sftp = 0;
 static int using_sftp = 0;
+static int uploading = 0;
 
 static Backend *back;
 static void *backhandle;
@@ -129,6 +130,19 @@ void modalfatalbox(char *fmt, ...)
 
     cleanup_exit(1);
 }
+void nonfatal(char *fmt, ...)
+{
+    char *str, *str2;
+    va_list ap;
+    va_start(ap, fmt);
+    str = dupvprintf(fmt, ap);
+    str2 = dupcat("Error: ", str, "\n", NULL);
+    sfree(str);
+    va_end(ap);
+    tell_str(stderr, str2);
+    sfree(str2);
+    errs++;
+}
 void connection_fatal(void *frontend, char *fmt, ...)
 {
     char *str, *str2;
@@ -218,11 +232,12 @@ int from_backend_untrusted(void *frontend_handle, const char *data, int len)
 int from_backend_eof(void *frontend)
 {
     /*
-     * We expect to be the party deciding when to close the
+     * We usually expect to be the party deciding when to close the
      * connection, so if we see EOF before we sent it ourselves, we
-     * should panic.
+     * should panic. The exception is if we're using old-style scp and
+     * downloading rather than uploading.
      */
-    if (!sent_eof) {
+    if ((using_sftp || uploading) && !sent_eof) {
         connection_fatal(frontend,
                          "Received unexpected end-of-file from server");
     }
@@ -320,6 +335,29 @@ static void bump(char *fmt, ...)
 }
 
 /*
+ * Wait for the reply to a single SFTP request. Parallels the same
+ * function in psftp.c (but isn't centralised into sftp.c because the
+ * latter module handles SFTP only and shouldn't assume that SFTP is
+ * the only thing going on by calling connection_fatal).
+ */
+struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req)
+{
+    struct sftp_packet *pktin;
+    struct sftp_request *rreq;
+
+    sftp_register(req);
+    pktin = sftp_recv();
+    if (pktin == NULL)
+        connection_fatal(NULL, "did not receive SFTP response packet "
+                         "from server");
+    rreq = sftp_find_request(pktin);
+    if (rreq != req)
+        connection_fatal(NULL, "unable to understand SFTP response packet "
+                         "from server: %s", fxp_error());
+    return pktin;
+}
+
+/*
  *  Open an SSH connection to user@host and execute cmd.
  */
 static void do_cmd(char *host, char *user, char *cmd)
@@ -503,7 +541,7 @@ static void do_cmd(char *host, char *user, char *cmd)
     console_provide_logctx(logctx);
     ssh_scp_init();
     if (verbose && realhost != NULL && errs == 0)
-       tell_user(stderr, "Connected to %s\n", realhost);
+       tell_user(stderr, "Connected to %s", realhost);
     sfree(realhost);
 }
 
@@ -652,7 +690,7 @@ static int response(void)
        } while (p < sizeof(rbuf) && ch != '\n');
        rbuf[p - 1] = '\0';
        if (resp == 1)
-           tell_user(stderr, "%s\n", rbuf);
+           tell_user(stderr, "%s", rbuf);
        else
            bump("%s", rbuf);
        errs++;
@@ -685,7 +723,7 @@ void scp_sftp_listdir(char *dirname)
     struct fxp_names *names;
     struct fxp_name *ournames;
     struct sftp_packet *pktin;
-    struct sftp_request *req, *rreq;
+    struct sftp_request *req;
     int nnames, namesize;
     int i;
 
@@ -697,10 +735,9 @@ void scp_sftp_listdir(char *dirname)
 
     printf("Listing directory %s\n", dirname);
 
-    sftp_register(req = fxp_opendir_send(dirname));
-    rreq = sftp_find_request(pktin = sftp_recv());
-    assert(rreq == req);
-    dirh = fxp_opendir_recv(pktin, rreq);
+    req = fxp_opendir_send(dirname);
+    pktin = sftp_wait_for_reply(req);
+    dirh = fxp_opendir_recv(pktin, req);
 
     if (dirh == NULL) {
        printf("Unable to open %s: %s\n", dirname, fxp_error());
@@ -710,10 +747,9 @@ void scp_sftp_listdir(char *dirname)
 
        while (1) {
 
-           sftp_register(req = fxp_readdir_send(dirh));
-           rreq = sftp_find_request(pktin = sftp_recv());
-           assert(rreq == req);
-           names = fxp_readdir_recv(pktin, rreq);
+           req = fxp_readdir_send(dirh);
+            pktin = sftp_wait_for_reply(req);
+           names = fxp_readdir_recv(pktin, req);
 
            if (names == NULL) {
                if (fxp_error_type() == SSH_FX_EOF)
@@ -736,22 +772,24 @@ void scp_sftp_listdir(char *dirname)
            names->nnames = 0;         /* prevent free_names */
            fxp_free_names(names);
        }
-       sftp_register(req = fxp_close_send(dirh));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       fxp_close_recv(pktin, rreq);
+       req = fxp_close_send(dirh);
+        pktin = sftp_wait_for_reply(req);
+       fxp_close_recv(pktin, req);
 
        /*
         * 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);
+        if (nnames > 0)
+            qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
 
        /*
         * And print them.
         */
        for (i = 0; i < nnames; i++)
            printf("%s\n", ournames[i].longname);
+
+        sfree(ournames);
     }
 }
 
@@ -786,7 +824,7 @@ int scp_source_setup(char *target, int shouldbedir)
         * directory.
         */
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
        struct fxp_attrs attrs;
        int ret;
 
@@ -796,10 +834,9 @@ int scp_source_setup(char *target, int shouldbedir)
            return 1;
        }
 
-       sftp_register(req = fxp_stat_send(target));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       ret = fxp_stat_recv(pktin, rreq, &attrs);
+       req = fxp_stat_send(target);
+        pktin = sftp_wait_for_reply(req);
+       ret = fxp_stat_recv(pktin, req, &attrs);
 
        if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
            scp_sftp_targetisdir = 0;
@@ -850,7 +887,7 @@ int scp_send_filename(char *name, uint64 size, int permissions)
     if (using_sftp) {
        char *fullname;
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
         struct fxp_attrs attrs;
 
        if (scp_sftp_targetisdir) {
@@ -862,16 +899,16 @@ int scp_send_filename(char *name, uint64 size, int permissions)
         attrs.flags = 0;
         PUT_PERMISSIONS(attrs, permissions);
 
-       sftp_register(req = fxp_open_send(fullname, SSH_FXF_WRITE |
-                                         SSH_FXF_CREAT | SSH_FXF_TRUNC,
-                                          &attrs));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       scp_sftp_filehandle = fxp_open_recv(pktin, rreq);
+       req = fxp_open_send(fullname,
+                            SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC,
+                            &attrs);
+        pktin = sftp_wait_for_reply(req);
+       scp_sftp_filehandle = fxp_open_recv(pktin, req);
 
        if (!scp_sftp_filehandle) {
            tell_user(stderr, "pscp: unable to open %s: %s",
                      fullname, fxp_error());
+            sfree(fullname);
            errs++;
            return 1;
        }
@@ -907,8 +944,10 @@ int scp_send_filedata(char *data, int len)
        while (!xfer_upload_ready(scp_sftp_xfer)) {
            pktin = sftp_recv();
            ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
-           if (!ret) {
-               tell_user(stderr, "error while writing: %s\n", fxp_error());
+           if (ret <= 0) {
+               tell_user(stderr, "error while writing: %s", fxp_error());
+                if (ret == INT_MIN)        /* pktin not even freed */
+                    sfree(pktin);
                errs++;
                return 1;
            }
@@ -942,12 +981,19 @@ int scp_send_finish(void)
     if (using_sftp) {
        struct fxp_attrs attrs;
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
        int ret;
 
        while (!xfer_done(scp_sftp_xfer)) {
            pktin = sftp_recv();
-           xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+           ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+           if (ret <= 0) {
+               tell_user(stderr, "error while writing: %s", fxp_error());
+                if (ret == INT_MIN)        /* pktin not even freed */
+                    sfree(pktin);
+               errs++;
+               return 1;
+           }
        }
        xfer_cleanup(scp_sftp_xfer);
 
@@ -958,19 +1004,17 @@ int scp_send_finish(void)
            attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
            attrs.atime = scp_sftp_atime;
            attrs.mtime = scp_sftp_mtime;
-           sftp_register(req = fxp_fsetstat_send(scp_sftp_filehandle, attrs));
-           rreq = sftp_find_request(pktin = sftp_recv());
-           assert(rreq == req);
-           ret = fxp_fsetstat_recv(pktin, rreq);
+           req = fxp_fsetstat_send(scp_sftp_filehandle, attrs);
+            pktin = sftp_wait_for_reply(req);
+           ret = fxp_fsetstat_recv(pktin, req);
            if (!ret) {
-               tell_user(stderr, "unable to set file times: %s\n", fxp_error());
+               tell_user(stderr, "unable to set file times: %s", fxp_error());
                errs++;
            }
        }
-       sftp_register(req = fxp_close_send(scp_sftp_filehandle));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       fxp_close_recv(pktin, rreq);
+       req = fxp_close_send(scp_sftp_filehandle);
+        pktin = sftp_wait_for_reply(req);
+       fxp_close_recv(pktin, req);
        scp_has_times = 0;
        return 0;
     } else {
@@ -1000,7 +1044,7 @@ int scp_send_dirname(char *name, int modes)
        char const *err;
        struct fxp_attrs attrs;
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
        int ret;
 
        if (scp_sftp_targetisdir) {
@@ -1016,25 +1060,24 @@ int scp_send_dirname(char *name, int modes)
         * exists and is a directory we will assume we were either
         * successful or it didn't matter.
         */
-       sftp_register(req = fxp_mkdir_send(fullname));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       ret = fxp_mkdir_recv(pktin, rreq);
+       req = fxp_mkdir_send(fullname);
+        pktin = sftp_wait_for_reply(req);
+       ret = fxp_mkdir_recv(pktin, req);
 
        if (!ret)
            err = fxp_error();
        else
            err = "server reported no error";
 
-       sftp_register(req = fxp_stat_send(fullname));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       ret = fxp_stat_recv(pktin, rreq, &attrs);
+       req = fxp_stat_send(fullname);
+        pktin = sftp_wait_for_reply(req);
+       ret = fxp_stat_recv(pktin, req, &attrs);
 
        if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
            !(attrs.permissions & 0040000)) {
            tell_user(stderr, "unable to create directory %s: %s",
                      fullname, err);
+            sfree(fullname);
            errs++;
            return 1;
        }
@@ -1090,6 +1133,9 @@ int scp_sink_setup(char *source, int preserve, int recursive)
        if (!wc_unescape(newsource, source)) {
            /* Yes, here we go; it's a wildcard. Bah. */
            char *dupsource, *lastpart, *dirpart, *wildcard;
+
+           sfree(newsource);
+
            dupsource = dupstr(source);
            lastpart = stripslashes(dupsource, 0);
            wildcard = dupstr(lastpart);
@@ -1179,7 +1225,7 @@ int scp_get_sink_action(struct scp_sink_action *act)
        int must_free_fname;
        struct fxp_attrs attrs;
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
        int ret;
 
        if (!scp_sftp_dirstack_head) {
@@ -1248,14 +1294,14 @@ int scp_get_sink_action(struct scp_sink_action *act)
         * Now we have a filename. Stat it, and see if it's a file
         * or a directory.
         */
-       sftp_register(req = fxp_stat_send(fname));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       ret = fxp_stat_recv(pktin, rreq, &attrs);
+       req = fxp_stat_send(fname);
+        pktin = sftp_wait_for_reply(req);
+       ret = fxp_stat_recv(pktin, req, &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());
+            if (must_free_fname) sfree(fname);
            errs++;
            return 1;
        }
@@ -1304,13 +1350,12 @@ int scp_get_sink_action(struct scp_sink_action *act)
             * list), we must push the other (target,namelist) pair
             * on a stack.
             */
-           sftp_register(req = fxp_opendir_send(fname));
-           rreq = sftp_find_request(pktin = sftp_recv());
-           assert(rreq == req);
-           dirhandle = fxp_opendir_recv(pktin, rreq);
+           req = fxp_opendir_send(fname);
+            pktin = sftp_wait_for_reply(req);
+           dirhandle = fxp_opendir_recv(pktin, req);
 
            if (!dirhandle) {
-               tell_user(stderr, "scp: unable to open directory %s: %s",
+               tell_user(stderr, "pscp: unable to open directory %s: %s",
                          fname, fxp_error());
                if (must_free_fname) sfree(fname);
                errs++;
@@ -1321,16 +1366,20 @@ int scp_get_sink_action(struct scp_sink_action *act)
            while (1) {
                int i;
 
-               sftp_register(req = fxp_readdir_send(dirhandle));
-               rreq = sftp_find_request(pktin = sftp_recv());
-               assert(rreq == req);
-               names = fxp_readdir_recv(pktin, rreq);
+               req = fxp_readdir_send(dirhandle);
+                pktin = sftp_wait_for_reply(req);
+               names = fxp_readdir_recv(pktin, req);
 
                if (names == NULL) {
                    if (fxp_error_type() == SSH_FX_EOF)
                        break;
-                   tell_user(stderr, "scp: reading directory %s: %s\n",
+                   tell_user(stderr, "pscp: reading directory %s: %s",
                              fname, fxp_error());
+
+                    req = fxp_close_send(dirhandle);
+                    pktin = sftp_wait_for_reply(req);
+                    fxp_close_recv(pktin, req);
+
                    if (must_free_fname) sfree(fname);
                    sfree(ournames);
                    errs++;
@@ -1354,7 +1403,7 @@ int scp_get_sink_action(struct scp_sink_action *act)
                         */
                    } else if (!vet_filename(names->names[i].filename)) {
                        tell_user(stderr, "ignoring potentially dangerous server-"
-                                 "supplied filename '%s'\n",
+                                 "supplied filename '%s'",
                                  names->names[i].filename);
                    } else
                        ournames[nnames++] = names->names[i];
@@ -1362,10 +1411,9 @@ int scp_get_sink_action(struct scp_sink_action *act)
                names->nnames = 0;             /* prevent free_names */
                fxp_free_names(names);
            }
-           sftp_register(req = fxp_close_send(dirhandle));
-           rreq = sftp_find_request(pktin = sftp_recv());
-           assert(rreq == req);
-           fxp_close_recv(pktin, rreq);
+           req = fxp_close_send(dirhandle);
+            pktin = sftp_wait_for_reply(req);
+           fxp_close_recv(pktin, req);
 
            newitem = snew(struct scp_sftp_dirstack);
            newitem->next = scp_sftp_dirstack_head;
@@ -1458,7 +1506,7 @@ int scp_get_sink_action(struct scp_sink_action *act)
            act->buf[i - 1] = '\0';
            switch (action) {
              case '\01':                      /* error */
-               tell_user(stderr, "%s\n", act->buf);
+               tell_user(stderr, "%s", act->buf);
                errs++;
                continue;                      /* go round again */
              case '\02':                      /* fatal error */
@@ -1510,13 +1558,11 @@ int scp_accept_filexfer(void)
 {
     if (using_sftp) {
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
 
-       sftp_register(req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ,
-                                          NULL));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       scp_sftp_filehandle = fxp_open_recv(pktin, rreq);
+       req = fxp_open_send(scp_sftp_currentname, SSH_FXF_READ, NULL);
+        pktin = sftp_wait_for_reply(req);
+       scp_sftp_filehandle = fxp_open_recv(pktin, req);
 
        if (!scp_sftp_filehandle) {
            tell_user(stderr, "pscp: unable to open %s: %s",
@@ -1545,9 +1591,10 @@ int scp_recv_filedata(char *data, int len)
        xfer_download_queue(scp_sftp_xfer);
        pktin = sftp_recv();
        ret = xfer_download_gotpkt(scp_sftp_xfer, pktin);
-
-       if (ret < 0) {
+       if (ret <= 0) {
            tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+            if (ret == INT_MIN)        /* pktin not even freed */
+                sfree(pktin);
            errs++;
            return -1;
        }
@@ -1577,7 +1624,7 @@ int scp_finish_filerecv(void)
 {
     if (using_sftp) {
        struct sftp_packet *pktin;
-       struct sftp_request *req, *rreq;
+       struct sftp_request *req;
 
        /*
         * Ensure that xfer_done() will work correctly, so we can
@@ -1587,19 +1634,25 @@ int scp_finish_filerecv(void)
        xfer_set_error(scp_sftp_xfer);
        while (!xfer_done(scp_sftp_xfer)) {
            void *vbuf;
-           int len;
+           int ret, len;
 
            pktin = sftp_recv();
-           xfer_download_gotpkt(scp_sftp_xfer, pktin);
+           ret = xfer_download_gotpkt(scp_sftp_xfer, pktin);
+            if (ret <= 0) {
+                tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+                if (ret == INT_MIN)        /* pktin not even freed */
+                    sfree(pktin);
+                errs++;
+                return -1;
+            }
            if (xfer_download_data(scp_sftp_xfer, &vbuf, &len))
                sfree(vbuf);
        }
        xfer_cleanup(scp_sftp_xfer);
 
-       sftp_register(req = fxp_close_send(scp_sftp_filehandle));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       fxp_close_recv(pktin, rreq);
+       req = fxp_close_send(scp_sftp_filehandle);
+        pktin = sftp_wait_for_reply(req);
+       fxp_close_recv(pktin, req);
        return 0;
     } else {
        back->send(backhandle, "", 1);
@@ -1618,7 +1671,7 @@ static void run_err(const char *fmt, ...)
     va_start(ap, fmt);
     errs++;
     str = dupvprintf(fmt, ap);
-    str2 = dupcat("scp: ", str, "\n", NULL);
+    str2 = dupcat("pscp: ", str, "\n", NULL);
     sfree(str);
     scp_send_errmsg(str2);
     tell_user(stderr, "%s", str2);
@@ -1687,8 +1740,10 @@ static void source(char *src)
        return;
     }
     if (preserve) {
-       if (scp_send_filetimes(mtime, atime))
+       if (scp_send_filetimes(mtime, atime)) {
+            close_rfile(f);
            return;
+        }
     }
 
     if (verbose) {
@@ -1696,8 +1751,10 @@ static void source(char *src)
        uint64_decimal(size, sizestr);
        tell_user(stderr, "Sending file %s, size=%s", last, sizestr);
     }
-    if (scp_send_filename(last, size, permissions))
+    if (scp_send_filename(last, size, permissions)) {
+        close_rfile(f);
        return;
+    }
 
     stat_bytes = uint64_make(0,0);
     stat_starttime = time(NULL);
@@ -1900,27 +1957,34 @@ static void sink(char *targ, char *src)
        if (act.action == SCP_SINK_DIR) {
            if (exists && attr != FILE_TYPE_DIRECTORY) {
                run_err("%s: Not a directory", destfname);
+                sfree(destfname);
                continue;
            }
            if (!exists) {
                if (!create_directory(destfname)) {
                    run_err("%s: Cannot create directory", destfname);
+                    sfree(destfname);
                    continue;
                }
            }
            sink(destfname, NULL);
            /* can we set the timestamp for directories ? */
+            sfree(destfname);
            continue;
        }
 
        f = open_new_file(destfname, act.permissions);
        if (f == NULL) {
            run_err("%s: Cannot create file", destfname);
+            sfree(destfname);
            continue;
        }
 
-       if (scp_accept_filexfer())
+       if (scp_accept_filexfer()) {
+            sfree(destfname);
+            close_wfile(f);
            return;
+        }
 
        stat_bytes = uint64_make(0, 0);
        stat_starttime = time(NULL);
@@ -1967,6 +2031,7 @@ static void sink(char *targ, char *src)
        close_wfile(f);
        if (wrerror) {
            run_err("%s: Write error", destfname);
+            sfree(destfname);
            continue;
        }
        (void) scp_finish_filerecv();
@@ -1984,6 +2049,8 @@ static void toremote(int argc, char *argv[])
     char *cmd;
     int i, wc_type;
 
+    uploading = 1;
+
     targ = argv[argc - 1];
 
     /* Separate host from filename */
@@ -2073,6 +2140,8 @@ static void tolocal(int argc, char *argv[])
     char *src, *targ, *host, *user;
     char *cmd;
 
+    uploading = 0;
+
     if (argc != 2)
        bump("More than one remote source not supported");
 
@@ -2283,9 +2352,12 @@ int psftp_main(int argc, char *argv[])
            preserve = 1;
        } else if (strcmp(argv[i], "-q") == 0) {
            statistics = 0;
-       } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0) {
+       } else if (strcmp(argv[i], "-h") == 0 ||
+                   strcmp(argv[i], "-?") == 0 ||
+                   strcmp(argv[i], "--help") == 0) {
            usage();
-       } else if (strcmp(argv[i], "-V") == 0) {
+       } else if (strcmp(argv[i], "-V") == 0 ||
+                   strcmp(argv[i], "--version") == 0) {
             version();
         } else if (strcmp(argv[i], "-ls") == 0) {
            list = 1;