New timing infrastructure. There's a new function schedule_timer()
[u/mdw/putty] / psftp.c
diff --git a/psftp.c b/psftp.c
index 5389659..1def80e 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -1,5 +1,5 @@
 /*
- * psftp.c: front end for PSFTP.
+ * psftp.c: (platform-independent) front end for PSFTP.
  */
 
 #include <stdio.h>
@@ -25,6 +25,7 @@
 
 static int psftp_connect(char *userhost, char *user, int portnumber);
 static int do_sftp_init(void);
+void do_sftp_cleanup();
 
 /* ----------------------------------------------------------------------
  * sftp client state.
@@ -370,6 +371,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart)
     struct fxp_handle *fh;
     struct sftp_packet *pktin;
     struct sftp_request *req, *rreq;
+    struct fxp_xfer *xfer;
     char *fname, *outfname;
     uint64 offset;
     FILE *fp;
@@ -439,41 +441,45 @@ int sftp_general_get(struct sftp_command *cmd, int restart)
      * thus put up a progress bar.
      */
     ret = 1;
-    while (1) {
-       char buffer[4096];
-       int len;
+    xfer = xfer_download_init(fh, offset);
+    while (!xfer_done(xfer)) {
+       void *vbuf;
+       int ret, len;
        int wpos, wlen;
 
-       sftp_register(req = fxp_read_send(fh, offset, sizeof(buffer)));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       len = fxp_read_recv(pktin, rreq, buffer, sizeof(buffer));
+       xfer_download_queue(xfer);
+       pktin = sftp_recv();
+       ret = xfer_download_gotpkt(xfer, pktin);
 
-       if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
-           break;
-       if (len == -1) {
-           printf("error while reading: %s\n", fxp_error());
-           ret = 0;
-           break;
+       if (ret < 0) {
+            printf("error while reading: %s\n", fxp_error());
+            ret = 0;
        }
 
-       wpos = 0;
-       while (wpos < len) {
-           wlen = fwrite(buffer, 1, len - wpos, fp);
-           if (wlen <= 0) {
-               printf("error while writing local file\n");
+       while (xfer_download_data(xfer, &vbuf, &len)) {
+           unsigned char *buf = (unsigned char *)vbuf;
+
+           wpos = 0;
+           while (wpos < len) {
+               wlen = fwrite(buf + wpos, 1, len - wpos, fp);
+               if (wlen <= 0) {
+                   printf("error while writing local file\n");
+                   ret = 0;
+                   xfer_set_error(xfer);
+               }
+               wpos += wlen;
+           }
+           if (wpos < len) {          /* we had an error */
                ret = 0;
-               break;
+               xfer_set_error(xfer);
            }
-           wpos += wlen;
-       }
-       if (wpos < len) {              /* we had an error */
-           ret = 0;
-           break;
+
+           sfree(vbuf);
        }
-       offset = uint64_add32(offset, len);
     }
 
+    xfer_cleanup(xfer);
+
     fclose(fp);
 
     sftp_register(req = fxp_close_send(fh));
@@ -503,12 +509,13 @@ int sftp_cmd_reget(struct sftp_command *cmd)
 int sftp_general_put(struct sftp_command *cmd, int restart)
 {
     struct fxp_handle *fh;
+    struct fxp_xfer *xfer;
     char *fname, *origoutfname, *outfname;
     struct sftp_packet *pktin;
     struct sftp_request *req, *rreq;
     uint64 offset;
     FILE *fp;
-    int ret;
+    int ret, err, eof;
 
     if (back == NULL) {
        printf("psftp: not connected to a host; use \"open host.name\"\n");
@@ -592,32 +599,36 @@ int sftp_general_put(struct sftp_command *cmd, int restart)
      * thus put up a progress bar.
      */
     ret = 1;
-    while (1) {
+    xfer = xfer_upload_init(fh, offset);
+    err = eof = 0;
+    while ((!err && !eof) || !xfer_done(xfer)) {
        char buffer[4096];
        int len, ret;
 
-       len = fread(buffer, 1, sizeof(buffer), fp);
-       if (len == -1) {
-           printf("error while reading local file\n");
-           ret = 0;
-           break;
-       } else if (len == 0) {
-           break;
+       while (xfer_upload_ready(xfer) && !err && !eof) {
+           len = fread(buffer, 1, sizeof(buffer), fp);
+           if (len == -1) {
+               printf("error while reading local file\n");
+               err = 1;
+           } else if (len == 0) {
+               eof = 1;
+           } else {
+               xfer_upload_data(xfer, buffer, len);
+           }
        }
 
-       sftp_register(req = fxp_write_send(fh, buffer, offset, len));
-       rreq = sftp_find_request(pktin = sftp_recv());
-       assert(rreq == req);
-       ret = fxp_write_recv(pktin, rreq);
-
-       if (!ret) {
-           printf("error while writing: %s\n", fxp_error());
-           ret = 0;
-           break;
+       if (!xfer_done(xfer)) {
+           pktin = sftp_recv();
+           ret = xfer_upload_gotpkt(xfer, pktin);
+           if (!ret) {
+               printf("error while writing: %s\n", fxp_error());
+               err = 1;
+           }
        }
-       offset = uint64_add32(offset, len);
     }
 
+    xfer_cleanup(xfer);
+
     sftp_register(req = fxp_close_send(fh));
     rreq = sftp_find_request(pktin = sftp_recv());
     assert(rreq == req);
@@ -1003,6 +1014,8 @@ int sftp_cmd_chmod(struct sftp_command *cmd)
 
 static int sftp_cmd_open(struct sftp_command *cmd)
 {
+    int portnumber;
+
     if (back != NULL) {
        printf("psftp: already connected\n");
        return 0;
@@ -1013,7 +1026,16 @@ static int sftp_cmd_open(struct sftp_command *cmd)
        return 0;
     }
 
-    if (psftp_connect(cmd->words[1], NULL, 0)) {
+    if (cmd->nwords > 2) {
+       portnumber = atoi(cmd->words[2]);
+       if (portnumber == 0) {
+           printf("open: invalid port number\n");
+           return 0;
+       }
+    } else
+       portnumber = 0;
+
+    if (psftp_connect(cmd->words[1], NULL, portnumber)) {
        back = NULL;                   /* connection is already closed */
        return -1;                     /* this is fatal */
     }
@@ -1205,7 +1227,7 @@ static struct sftp_cmd_lookup {
     },
     {
        "open", TRUE, "connect to a host",
-           " [<user>@]<hostname>\n"
+           " [<user>@]<hostname> [<port>]\n"
            "  Establishes an SFTP connection to a given host. Only usable\n"
            "  when you did not already specify a host name on the command\n"
            "  line.\n",
@@ -1339,45 +1361,34 @@ static int sftp_cmd_help(struct sftp_command *cmd)
 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
 {
     char *line;
-    int linelen, linesize;
     struct sftp_command *cmd;
     char *p, *q, *r;
     int quoting;
 
-    if ((mode == 0) || (modeflags & 1)) {
-       printf("psftp> ");
-    }
-    fflush(stdout);
-
     cmd = snew(struct sftp_command);
     cmd->words = NULL;
     cmd->nwords = 0;
     cmd->wordssize = 0;
 
     line = NULL;
-    linesize = linelen = 0;
-    while (1) {
-       int len;
-       char *ret;
-
-       linesize += 512;
-       line = sresize(line, linesize, char);
-       ret = fgets(line + linelen, linesize - linelen, fp);
-
-       if (!ret || (linelen == 0 && line[0] == '\0')) {
-           cmd->obey = sftp_cmd_quit;
-           if ((mode == 0) || (modeflags & 1))
-               printf("quit\n");
-           return cmd;                /* eof */
-       }
-       len = linelen + strlen(line + linelen);
-       linelen += len;
-       if (line[linelen - 1] == '\n') {
-           linelen--;
-           line[linelen] = '\0';
-           break;
-       }
+
+    if (fp) {
+       if (modeflags & 1)
+           printf("psftp> ");
+       line = fgetline(fp);
+    } else {
+       line = ssh_sftp_get_cmdline("psftp> ");
     }
+
+    if (!line || !*line) {
+       cmd->obey = sftp_cmd_quit;
+       if ((mode == 0) || (modeflags & 1))
+           printf("quit\n");
+       return cmd;                    /* eof */
+    }
+
+    line[strcspn(line, "\r\n")] = '\0';
+
     if (modeflags & 1) {
        printf("%s\n", line);
     }
@@ -1394,8 +1405,8 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
         */
        cmd->nwords = cmd->wordssize = 2;
        cmd->words = sresize(cmd->words, cmd->wordssize, char *);
-       cmd->words[0] = "!";
-       cmd->words[1] = p+1;
+       cmd->words[0] = dupstr("!");
+       cmd->words[1] = dupstr(p+1);
     } else {
 
        /*
@@ -1438,10 +1449,12 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
                cmd->wordssize = cmd->nwords + 16;
                cmd->words = sresize(cmd->words, cmd->wordssize, char *);
            }
-           cmd->words[cmd->nwords++] = q;
+           cmd->words[cmd->nwords++] = dupstr(q);
        }
     }
 
+    sfree(line);
+
     /*
      * Now parse the first word and assign a function.
      */
@@ -1494,6 +1507,25 @@ static int do_sftp_init(void)
     return 0;
 }
 
+void do_sftp_cleanup()
+{
+    char ch;
+    if (back) {
+       back->special(backhandle, TS_EOF);
+       sftp_recvdata(&ch, 1);
+       back->free(backhandle);
+       sftp_cleanup_request();
+    }
+    if (pwd) {
+       sfree(pwd);
+       pwd = NULL;
+    }
+    if (homedir) {
+       sfree(homedir);
+       homedir = NULL;
+    }
+}
+
 void do_sftp(int mode, int modeflags, char *batchfile)
 {
     FILE *fp;
@@ -1509,10 +1541,18 @@ void do_sftp(int mode, int modeflags, char *batchfile)
          */
         while (1) {
            struct sftp_command *cmd;
-           cmd = sftp_getcmd(stdin, 0, 0);
+           cmd = sftp_getcmd(NULL, 0, 0);
            if (!cmd)
                break;
-           if (cmd->obey(cmd) < 0)
+           ret = cmd->obey(cmd);
+           if (cmd->words) {
+               int i;
+               for(i = 0; i < cmd->nwords; i++)
+                   sfree(cmd->words[i]);
+               sfree(cmd->words);
+           }
+           sfree(cmd);
+           if (ret < 0)
                break;
        }
     } else {
@@ -1630,14 +1670,13 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
     unsigned char *p = (unsigned char *) data;
     unsigned len = (unsigned) datalen;
 
-    assert(len > 0);
-
     /*
      * stderr data is just spouted to local stderr and otherwise
      * ignored.
      */
     if (is_stderr) {
-       fwrite(data, 1, len, stderr);
+       if (len > 0)
+           fwrite(data, 1, len, stderr);
        return 0;
     }
 
@@ -1647,7 +1686,7 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
     if (!outptr)
        return 0;
 
-    if (outlen > 0) {
+    if ((outlen > 0) && (len > 0)) {
        unsigned used = outlen;
        if (used > len)
            used = len;
@@ -1705,7 +1744,7 @@ int sftp_recvdata(char *buf, int len)
 }
 int sftp_senddata(char *buf, int len)
 {
-    back->send(backhandle, (unsigned char *) buf, len);
+    back->send(backhandle, buf, len);
     return 1;
 }
 
@@ -1716,7 +1755,7 @@ static void usage(void)
 {
     printf("PuTTY Secure File Transfer (SFTP) client\n");
     printf("%s\n", ver);
-    printf("Usage: psftp [options] user@host\n");
+    printf("Usage: psftp [options] [user@]host\n");
     printf("Options:\n");
     printf("  -b file   use specified batchfile\n");
     printf("  -bc       output batchfile commands\n");
@@ -1730,9 +1769,16 @@ static void usage(void)
     printf("  -C        enable compression\n");
     printf("  -i key    private key file for authentication\n");
     printf("  -batch    disable all interactive prompts\n");
+    printf("  -V        print version information\n");
     cleanup_exit(1);
 }
 
+static void version(void)
+{
+  printf("psftp: %s\n", ver);
+  cleanup_exit(1);
+}
+
 /*
  * Connect to a host.
  */
@@ -1756,11 +1802,27 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
            user = userhost;
     }
 
-    /* Try to load settings for this host */
-    do_defaults(host, &cfg);
-    if (cfg.host[0] == '\0') {
-       /* No settings for this host; use defaults */
-       do_defaults(NULL, &cfg);
+    /*
+     * If we haven't loaded session details already (e.g., from -load),
+     * try looking for a session called "host".
+     */
+    if (!loaded_session) {
+       /* Try to load settings for `host' into a temporary config */
+       Config cfg2;
+       cfg2.host[0] = '\0';
+       do_defaults(host, &cfg2);
+       if (cfg2.host[0] != '\0') {
+           /* Settings present and include hostname */
+           /* Re-load data into the real config. */
+           do_defaults(host, &cfg);
+       } else {
+           /* Session doesn't exist or mention a hostname. */
+           /* Use `host' as a bare hostname. */
+           strncpy(cfg.host, host, sizeof(cfg.host) - 1);
+           cfg.host[sizeof(cfg.host) - 1] = '\0';
+       }
+    } else {
+       /* Patch in hostname `host' to session details. */
        strncpy(cfg.host, host, sizeof(cfg.host) - 1);
        cfg.host[sizeof(cfg.host) - 1] = '\0';
     }
@@ -1775,6 +1837,15 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
     }
 
     /*
+     * If saved session / Default Settings says SSH-1 (`1 only' or `1'),
+     * then change it to SSH-2, on the grounds that that's more likely to
+     * work for SFTP. (Can be overridden with `-1' option.)
+     * But if it says `2 only' or `2', respect which.
+     */
+    if (cfg.sshprot != 2 && cfg.sshprot != 3)
+       cfg.sshprot = 2;
+
+    /*
      * Enact command-line overrides.
      */
     cmdline_run_saved(&cfg);
@@ -1789,7 +1860,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
 
     /* See if host is of the form user@host */
     if (cfg.host[0] != '\0') {
-       char *atsign = strchr(cfg.host, '@');
+       char *atsign = strrchr(cfg.host, '@');
        /* Make sure we're not overflowing the user field */
        if (atsign) {
            if (atsign - cfg.host < sizeof cfg.username) {
@@ -1826,10 +1897,9 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
        cfg.username[sizeof(cfg.username) - 1] = '\0';
     }
     if (!cfg.username[0]) {
-       printf("login as: ");
-       fflush(stdout);
-       if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
-           fprintf(stderr, "psftp: aborting\n");
+       if (!console_get_line("login as: ",
+                             cfg.username, sizeof(cfg.username), FALSE)) {
+           fprintf(stderr, "psftp: no username, aborting\n");
            cleanup_exit(1);
        } else {
            int len = strlen(cfg.username);
@@ -1841,9 +1911,6 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
     if (portnumber)
        cfg.port = portnumber;
 
-    /* SFTP uses SSH2 by default always */
-    cfg.sshprot = 2;
-
     /*
      * Disable scary things which shouldn't be enabled for simple
      * things like SCP and SFTP: agent forwarding, port forwarding,
@@ -1883,7 +1950,8 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
 
     back = &ssh_backend;
 
-    err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,0);
+    err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
+                    0, cfg.tcp_keepalives);
     if (err != NULL) {
        fprintf(stderr, "ssh_init: %s\n", err);
        return 1;
@@ -1899,6 +1967,8 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
     }
     if (verbose && realhost != NULL)
        printf("Connected to %s\n", realhost);
+    if (realhost != NULL)
+       sfree(realhost);
     return 0;
 }
 
@@ -1937,6 +2007,10 @@ int psftp_main(int argc, char *argv[])
 
     userhost = user = NULL;
 
+    /* Load Default Settings before doing anything else. */
+    do_defaults(NULL, &cfg);
+    loaded_session = FALSE;
+
     errors = 0;
     for (i = 1; i < argc; i++) {
        int ret;
@@ -1959,6 +2033,8 @@ int psftp_main(int argc, char *argv[])
        } else if (strcmp(argv[i], "-h") == 0 ||
                   strcmp(argv[i], "-?") == 0) {
            usage();
+       } else if (strcmp(argv[i], "-V") == 0) {
+           version();
        } else if (strcmp(argv[i], "-batch") == 0) {
            console_batch_mode = 1;
        } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
@@ -1980,17 +2056,29 @@ int psftp_main(int argc, char *argv[])
     back = NULL;
 
     /*
+     * If the loaded session provides a hostname, and a hostname has not
+     * otherwise been specified, pop it in `userhost' so that
+     * `psftp -load sessname' is sufficient to start a session.
+     */
+    if (!userhost && cfg.host[0] != '\0') {
+       userhost = dupstr(cfg.host);
+    }
+
+    /*
      * If a user@host string has already been provided, connect to
      * it now.
      */
     if (userhost) {
-       if (psftp_connect(userhost, user, portnumber))
+       int ret;
+       ret = psftp_connect(userhost, user, portnumber);
+       sfree(userhost);
+       if (ret)
            return 1;
        if (do_sftp_init())
            return 1;
     } else {
        printf("psftp: no hostname specified; use \"open host.name\""
-           " to connect\n");
+              " to connect\n");
     }
 
     do_sftp(mode, modeflags, batchfile);
@@ -2001,6 +2089,12 @@ int psftp_main(int argc, char *argv[])
        sftp_recvdata(&ch, 1);
     }
     random_save_seed();
+    cmdline_cleanup();
+    console_provide_logctx(NULL);
+    do_sftp_cleanup();
+    backhandle = NULL;
+    back = NULL;
+    sk_cleanup();
 
     return 0;
 }