More upgrades to psftp: it now supports mv, chmod, reget and reput.
[u/mdw/putty] / psftp.c
diff --git a/psftp.c b/psftp.c
index fbf730a..edc4bed 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -8,6 +8,7 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include <assert.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <assert.h>
+#include <limits.h>
 
 #define PUTTY_DO_GLOBALS
 #include "putty.h"
 
 #define PUTTY_DO_GLOBALS
 #include "putty.h"
 #include "sftp.h"
 #include "int64.h"
 
 #include "sftp.h"
 #include "int64.h"
 
+/*
+ * Since SFTP is a request-response oriented protocol, it requires
+ * no buffer management: when we send data, we stop and wait for an
+ * acknowledgement _anyway_, and so we can't possibly overfill our
+ * send buffer.
+ */
+
 /* ----------------------------------------------------------------------
  * String handling routines.
  */
 /* ----------------------------------------------------------------------
  * String handling routines.
  */
@@ -314,9 +322,12 @@ int sftp_cmd_cd(struct sftp_command *cmd)
 }
 
 /*
 }
 
 /*
- * Get a file and save it at the local end.
+ * Get a file and save it at the local end. We have two very
+ * similar commands here: `get' and `reget', which differ in that
+ * `reget' checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off.
  */
  */
-int sftp_cmd_get(struct sftp_command *cmd)
+int sftp_general_get(struct sftp_command *cmd, int restart)
 {
     struct fxp_handle *fh;
     char *fname, *outfname;
 {
     struct fxp_handle *fh;
     char *fname, *outfname;
@@ -341,7 +352,13 @@ int sftp_cmd_get(struct sftp_command *cmd)
        sfree(fname);
        return 0;
     }
        sfree(fname);
        return 0;
     }
-    fp = fopen(outfname, "wb");
+
+    if (restart) {
+       fp = fopen(outfname, "rb+");
+    } else {
+       fp = fopen(outfname, "wb");
+    }
+
     if (!fp) {
        printf("local: unable to open %s\n", outfname);
        fxp_close(fh);
     if (!fp) {
        printf("local: unable to open %s\n", outfname);
        fxp_close(fh);
@@ -349,9 +366,17 @@ int sftp_cmd_get(struct sftp_command *cmd)
        return 0;
     }
 
        return 0;
     }
 
-    printf("remote:%s => local:%s\n", fname, outfname);
+    if (restart) {
+       long posn;
+       fseek(fp, 0L, SEEK_END);
+       posn = ftell(fp);
+       printf("reget: restarting at file position %ld\n", posn);
+       offset = uint64_make(0, posn);
+    } else {
+       offset = uint64_make(0, 0);
+    }
 
 
-    offset = uint64_make(0, 0);
+    printf("remote:%s => local:%s\n", fname, outfname);
 
     /*
      * FIXME: we can use FXP_FSTAT here to get the file size, and
 
     /*
      * FIXME: we can use FXP_FSTAT here to get the file size, and
@@ -390,11 +415,22 @@ int sftp_cmd_get(struct sftp_command *cmd)
 
     return 0;
 }
 
     return 0;
 }
+int sftp_cmd_get(struct sftp_command *cmd)
+{
+    return sftp_general_get(cmd, 0);
+}
+int sftp_cmd_reget(struct sftp_command *cmd)
+{
+    return sftp_general_get(cmd, 1);
+}
 
 /*
 
 /*
- * Send a file and store it at the remote end.
+ * Send a file and store it at the remote end. We have two very
+ * similar commands here: `put' and `reput', which differ in that
+ * `reput' checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off.
  */
  */
-int sftp_cmd_put(struct sftp_command *cmd)
+int sftp_general_put(struct sftp_command *cmd, int restart)
 {
     struct fxp_handle *fh;
     char *fname, *origoutfname, *outfname;
 {
     struct fxp_handle *fh;
     char *fname, *origoutfname, *outfname;
@@ -420,16 +456,47 @@ int sftp_cmd_put(struct sftp_command *cmd)
        sfree(outfname);
        return 0;
     }
        sfree(outfname);
        return 0;
     }
-    fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
+    if (restart) {
+       fh = fxp_open(outfname,
+                     SSH_FXF_WRITE);
+    } else {
+       fh = fxp_open(outfname,
+                     SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
+    }
     if (!fh) {
        printf("%s: %s\n", outfname, fxp_error());
        sfree(outfname);
        return 0;
     }
 
     if (!fh) {
        printf("%s: %s\n", outfname, fxp_error());
        sfree(outfname);
        return 0;
     }
 
-    printf("local:%s => remote:%s\n", fname, outfname);
+    if (restart) {
+       char decbuf[30];
+       struct fxp_attrs attrs;
+       if (!fxp_fstat(fh, &attrs)) {
+           printf("read size of %s: %s\n", outfname, fxp_error());
+           sfree(outfname);
+           return 0;
+       }
+       if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
+           printf("read size of %s: size was not given\n", outfname);
+           sfree(outfname);
+           return 0;
+       }
+       offset = attrs.size;
+       uint64_decimal(offset, decbuf);
+       printf("reput: restarting at file position %s\n", decbuf);
+       if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
+           printf("reput: remote file is larger than we can deal with\n");
+           sfree(outfname);
+           return 0;
+       }
+       if (fseek(fp, offset.lo, SEEK_SET) != 0)
+           fseek(fp, 0, SEEK_END);    /* *shrug* */
+    } else {
+       offset = uint64_make(0, 0);
+    }
 
 
-    offset = uint64_make(0, 0);
+    printf("local:%s => remote:%s\n", fname, outfname);
 
     /*
      * FIXME: we can use FXP_FSTAT here to get the file size, and
 
     /*
      * FIXME: we can use FXP_FSTAT here to get the file size, and
@@ -459,6 +526,314 @@ int sftp_cmd_put(struct sftp_command *cmd)
 
     return 0;
 }
 
     return 0;
 }
+int sftp_cmd_put(struct sftp_command *cmd)
+{
+    return sftp_general_put(cmd, 0);
+}
+int sftp_cmd_reput(struct sftp_command *cmd)
+{
+    return sftp_general_put(cmd, 1);
+}
+
+int sftp_cmd_mkdir(struct sftp_command *cmd)
+{
+    char *dir;
+    int result;
+
+
+    if (cmd->nwords < 2) {
+       printf("mkdir: expects a directory\n");
+       return 0;
+    }
+
+    dir = canonify(cmd->words[1]);
+    if (!dir) {
+       printf("%s: %s\n", dir, fxp_error());
+       return 0;
+    }
+
+    result = fxp_mkdir(dir);
+    if (!result) {
+       printf("mkdir %s: %s\n", dir, fxp_error());
+       sfree(dir);
+       return 0;
+    }
+
+    sfree(dir);
+    return 0;
+}
+
+int sftp_cmd_rmdir(struct sftp_command *cmd)
+{
+    char *dir;
+    int result;
+
+
+    if (cmd->nwords < 2) {
+       printf("rmdir: expects a directory\n");
+       return 0;
+    }
+
+    dir = canonify(cmd->words[1]);
+    if (!dir) {
+       printf("%s: %s\n", dir, fxp_error());
+       return 0;
+    }
+
+    result = fxp_rmdir(dir);
+    if (!result) {
+       printf("rmdir %s: %s\n", dir, fxp_error());
+       sfree(dir);
+       return 0;
+    }
+
+    sfree(dir);
+    return 0;
+}
+
+int sftp_cmd_rm(struct sftp_command *cmd)
+{
+    char *fname;
+    int result;
+
+    if (cmd->nwords < 2) {
+       printf("rm: expects a filename\n");
+       return 0;
+    }
+
+    fname = canonify(cmd->words[1]);
+    if (!fname) {
+       printf("%s: %s\n", fname, fxp_error());
+       return 0;
+    }
+
+    result = fxp_remove(fname);
+    if (!result) {
+       printf("rm %s: %s\n", fname, fxp_error());
+       sfree(fname);
+       return 0;
+    }
+
+    sfree(fname);
+    return 0;
+
+}
+
+int sftp_cmd_mv(struct sftp_command *cmd)
+{
+    char *srcfname, *dstfname;
+    int result;
+
+    if (cmd->nwords < 3) {
+       printf("mv: expects two filenames\n");
+       return 0;
+    }
+    srcfname = canonify(cmd->words[1]);
+    if (!srcfname) {
+       printf("%s: %s\n", srcfname, fxp_error());
+       return 0;
+    }
+
+    dstfname = canonify(cmd->words[2]);
+    if (!dstfname) {
+       printf("%s: %s\n", dstfname, fxp_error());
+       return 0;
+    }
+
+    result = fxp_rename(srcfname, dstfname);
+    if (!result) {
+       char const *error = fxp_error();
+       struct fxp_attrs attrs;
+
+       /*
+        * The move might have failed because dstfname pointed at a
+        * directory. We check this possibility now: if dstfname
+        * _is_ a directory, we re-attempt the move by appending
+        * the basename of srcfname to dstfname.
+        */
+       result = fxp_stat(dstfname, &attrs);
+       if (result &&
+           (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
+           (attrs.permissions & 0040000)) {
+           char *p;
+           char *newname, *newcanon;
+           printf("(destination %s is a directory)\n", dstfname);
+           p = srcfname + strlen(srcfname);
+           while (p > srcfname && p[-1] != '/') p--;
+           newname = dupcat(dstfname, "/", p, NULL);
+           newcanon = canonify(newname);
+           sfree(newname);
+           if (newcanon) {
+               sfree(dstfname);
+               dstfname = newcanon;
+               result = fxp_rename(srcfname, dstfname);
+               error = result ? NULL : fxp_error();
+           }
+       }
+       if (error) {
+           printf("mv %s %s: %s\n", srcfname, dstfname, error);
+           sfree(srcfname);
+           sfree(dstfname);
+           return 0;
+       }
+    }
+    printf("%s -> %s\n", srcfname, dstfname);
+
+    sfree(srcfname);
+    sfree(dstfname);
+    return 0;
+}
+
+int sftp_cmd_chmod(struct sftp_command *cmd)
+{
+    char *fname, *mode;
+    int result;
+    struct fxp_attrs attrs;
+    unsigned attrs_clr, attrs_xor, oldperms, newperms;
+
+    if (cmd->nwords < 3) {
+       printf("chmod: expects a mode specifier and a filename\n");
+       return 0;
+    }
+
+    /*
+     * Attempt to parse the mode specifier in cmd->words[1]. We
+     * don't support the full horror of Unix chmod; instead we
+     * support a much simpler syntax in which the user can either
+     * specify an octal number, or a comma-separated sequence of
+     * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
+     * _only_ be omitted if the only attribute mentioned is t,
+     * since all others require a user/group/other specification.
+     * Additionally, the s attribute may not be specified for any
+     * [ugoa] specifications other than exactly u or exactly g.
+     */
+    attrs_clr = attrs_xor = 0;
+    mode = cmd->words[1];
+    if (mode[0] >= '0' && mode[0] <= '9') {
+       if (mode[strspn(mode, "01234567")]) {
+           printf("chmod: numeric file modes should"
+                  " contain digits 0-7 only\n");
+           return 0;
+       }
+       attrs_clr = 07777;
+       sscanf(mode, "%o", &attrs_xor);
+       attrs_xor &= attrs_clr;
+    } else {
+       while (*mode) {
+           char *modebegin = mode;
+           unsigned subset, perms;
+           int action;
+
+           subset = 0;
+           while (*mode && *mode != ',' &&
+                  *mode != '+' && *mode != '-' && *mode != '=') {
+               switch (*mode) {
+                 case 'u': subset |= 04700; break; /* setuid, user perms */
+                 case 'g': subset |= 02070; break; /* setgid, group perms */
+                 case 'o': subset |= 00007; break; /* just other perms */
+                 case 'a': subset |= 06777; break; /* all of the above */
+                 default:
+                   printf("chmod: file mode '%.*s' contains unrecognised"
+                          " user/group/other specifier '%c'\n",
+                          strcspn(modebegin, ","), modebegin, *mode);
+                   return 0;
+               }
+               mode++;
+           }
+           if (!*mode || *mode == ',') {
+               printf("chmod: file mode '%.*s' is incomplete\n",
+                      strcspn(modebegin, ","), modebegin);
+               return 0;
+           }
+           action = *mode++;
+           if (!*mode || *mode == ',') {
+               printf("chmod: file mode '%.*s' is incomplete\n",
+                      strcspn(modebegin, ","), modebegin);
+               return 0;
+           }
+           perms = 0;
+           while (*mode && *mode != ',') {
+               switch (*mode) {
+                 case 'r': perms |= 00444; break;
+                 case 'w': perms |= 00222; break;
+                 case 'x': perms |= 00111; break;
+                 case 't': perms |= 01000; subset |= 01000; break;
+                 case 's':
+                   if ((subset & 06777) != 04700 &&
+                       (subset & 06777) != 02070) {
+                       printf("chmod: file mode '%.*s': set[ug]id bit should"
+                              " be used with exactly one of u or g only\n",
+                              strcspn(modebegin, ","), modebegin);
+                       return 0;
+                   }
+                   perms |= 06000;
+                   break;
+                 default:
+                   printf("chmod: file mode '%.*s' contains unrecognised"
+                          " permission specifier '%c'\n",
+                          strcspn(modebegin, ","), modebegin, *mode);
+                   return 0;
+               }
+               mode++;
+           }
+           if (!(subset & 06777) && (perms &~ subset)) {
+               printf("chmod: file mode '%.*s' contains no user/group/other"
+                      " specifier and permissions other than 't' \n",
+                      strcspn(modebegin, ","), modebegin, *mode);
+               return 0;
+           }
+           perms &= subset;
+           switch (action) {
+             case '+':
+               attrs_clr |= perms;
+               attrs_xor |= perms;
+               break;
+             case '-':
+               attrs_clr |= perms;
+               attrs_xor &= ~perms;
+               break;
+             case '=':
+               attrs_clr |= subset;
+               attrs_xor |= perms;
+               break;
+           }
+           if (*mode) mode++;         /* eat comma */
+       }
+    }
+
+    fname = canonify(cmd->words[2]);
+    if (!fname) {
+       printf("%s: %s\n", fname, fxp_error());
+       return 0;
+    }
+
+    result = fxp_stat(fname, &attrs);
+    if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+       printf("get attrs for %s: %s\n", fname,
+              result ? "file permissions not provided" : fxp_error());
+       sfree(fname);
+       return 0;
+    }
+
+    attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;   /* perms _only_ */
+    oldperms = attrs.permissions & 07777;
+    attrs.permissions &= ~attrs_clr;
+    attrs.permissions ^= attrs_xor;
+    newperms = attrs.permissions & 07777;
+
+    result = fxp_setstat(fname, attrs);
+
+    if (!result) {
+       printf("set attrs for %s: %s\n", fname, fxp_error());
+       sfree(fname);
+       return 0;
+    }
+
+    printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
+
+    sfree(fname);
+    return 0;
+}
 
 static struct sftp_cmd_lookup {
     char *name;
 
 static struct sftp_cmd_lookup {
     char *name;
@@ -471,17 +846,28 @@ static struct sftp_cmd_lookup {
     {
     "bye", sftp_cmd_quit}, {
     "cd", sftp_cmd_cd}, {
     {
     "bye", sftp_cmd_quit}, {
     "cd", sftp_cmd_cd}, {
+    "chmod", sftp_cmd_chmod}, {
+    "del", sftp_cmd_rm}, {
+    "delete", sftp_cmd_rm}, {
     "dir", sftp_cmd_ls}, {
     "exit", sftp_cmd_quit}, {
     "get", sftp_cmd_get}, {
     "ls", sftp_cmd_ls}, {
     "dir", sftp_cmd_ls}, {
     "exit", sftp_cmd_quit}, {
     "get", sftp_cmd_get}, {
     "ls", sftp_cmd_ls}, {
+    "mkdir", sftp_cmd_mkdir}, {
+    "mv", sftp_cmd_mv}, {
     "put", sftp_cmd_put}, {
     "put", sftp_cmd_put}, {
-"quit", sftp_cmd_quit},};
+    "quit", sftp_cmd_quit}, {
+    "reget", sftp_cmd_reget}, {
+    "ren", sftp_cmd_mv}, {
+    "rename", sftp_cmd_mv}, {
+    "reput", sftp_cmd_reput}, {
+    "rm", sftp_cmd_rm}, {
+    "rmdir", sftp_cmd_rmdir},};
 
 /* ----------------------------------------------------------------------
  * Command line reading and parsing.
  */
 
 /* ----------------------------------------------------------------------
  * Command line reading and parsing.
  */
-struct sftp_command *sftp_getcmd(void)
+struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
 {
     char *line;
     int linelen, linesize;
 {
     char *line;
     int linelen, linesize;
@@ -489,7 +875,9 @@ struct sftp_command *sftp_getcmd(void)
     char *p, *q, *r;
     int quoting;
 
     char *p, *q, *r;
     int quoting;
 
-    printf("psftp> ");
+       if ((mode == 0) || (modeflags & 1)) {
+           printf("psftp> ");
+       }
     fflush(stdout);
 
     cmd = smalloc(sizeof(struct sftp_command));
     fflush(stdout);
 
     cmd = smalloc(sizeof(struct sftp_command));
@@ -505,7 +893,10 @@ struct sftp_command *sftp_getcmd(void)
 
        linesize += 512;
        line = srealloc(line, linesize);
 
        linesize += 512;
        line = srealloc(line, linesize);
-       ret = fgets(line + linelen, linesize - linelen, stdin);
+       ret = fgets(line + linelen, linesize - linelen, fp);
+       if (modeflags & 1) {
+               printf("%s", ret);
+       }
 
        if (!ret || (linelen == 0 && line[0] == '\0')) {
            cmd->obey = sftp_cmd_quit;
 
        if (!ret || (linelen == 0 && line[0] == '\0')) {
            cmd->obey = sftp_cmd_quit;
@@ -596,8 +987,10 @@ struct sftp_command *sftp_getcmd(void)
     return cmd;
 }
 
     return cmd;
 }
 
-void do_sftp(void)
+void do_sftp(int mode, int modeflags, char *batchfile)
 {
 {
+    FILE *fp;
+
     /*
      * Do protocol initialisation. 
      */
     /*
      * Do protocol initialisation. 
      */
@@ -621,16 +1014,42 @@ void do_sftp(void)
     }
     pwd = dupstr(homedir);
 
     }
     pwd = dupstr(homedir);
 
-    /* ------------------------------------------------------------------
-     * Now we're ready to do Real Stuff.
+    /*
+     * Batch mode?
      */
      */
-    while (1) {
-       struct sftp_command *cmd;
-       cmd = sftp_getcmd();
-       if (!cmd)
-           break;
-       if (cmd->obey(cmd) < 0)
-           break;
+    if (mode == 0) {
+
+        /* ------------------------------------------------------------------
+         * Now we're ready to do Real Stuff.
+         */
+        while (1) {
+       struct sftp_command *cmd;
+       cmd = sftp_getcmd(stdin, 0, 0);
+       if (!cmd)
+           break;
+               if (cmd->obey(cmd) < 0)
+                   break;
+           }
+    } else {
+        fp = fopen(batchfile, "r");
+        if (!fp) {
+        printf("Fatal: unable to open %s\n", batchfile);
+        return;
+        }
+        while (1) {
+       struct sftp_command *cmd;
+       cmd = sftp_getcmd(fp, mode, modeflags);
+       if (!cmd)
+           break;
+               if (cmd->obey(cmd) < 0)
+                   break;
+               if (fxp_error() != NULL) {
+                       if (!(modeflags & 2))
+                               break;
+               }
+        }
+           fclose(fp);
+
     }
 }
 
     }
 }
 
@@ -644,6 +1063,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"
@@ -653,8 +1074,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"
@@ -666,9 +1090,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"
@@ -685,26 +1109,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);
     if (ret == 2) {                   /* key was different */
        fprintf(stderr, wrongmsg, fingerprint);
-       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);
-           exit(0);
-       }
+       fflush(stderr);
     }
     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);
     }
 }
 
     }
 }
 
@@ -780,7 +1247,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;
@@ -791,14 +1258,14 @@ 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;
     }
 
     /*
      * 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;
@@ -822,6 +1289,8 @@ 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;
 }
 int sftp_recvdata(char *buf, int len)
 {
 }
 int sftp_recvdata(char *buf, int len)
 {
@@ -964,6 +1433,9 @@ static void usage(void)
     printf("%s\n", ver);
     printf("Usage: psftp [options] user@host\n");
     printf("Options:\n");
     printf("%s\n", ver);
     printf("Usage: psftp [options] user@host\n");
     printf("Options:\n");
+    printf("  -b file   use specified batchfile\n");
+    printf("  -bc       output batchfile commands\n");
+    printf("  -be       don't stop batchfile processing if errors\n");
     printf("  -v        show verbose messages\n");
     printf("  -P port   connect to specified port\n");
     printf("  -pw passw login with specified password\n");
     printf("  -v        show verbose messages\n");
     printf("  -P port   connect to specified port\n");
     printf("  -pw passw login with specified password\n");
@@ -979,6 +1451,9 @@ int main(int argc, char *argv[])
     int portnumber = 0;
     char *user, *host, *userhost, *realhost;
     char *err;
     int portnumber = 0;
     char *user, *host, *userhost, *realhost;
     char *err;
+    int mode = 0;
+    int modeflags = 0;
+    char *batchfile = NULL;
 
     flags = FLAG_STDERR;
     ssh_get_line = &get_line;
 
     flags = FLAG_STDERR;
     ssh_get_line = &get_line;
@@ -1004,6 +1479,13 @@ int main(int argc, char *argv[])
            portnumber = atoi(argv[++i]);
        } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
            password = argv[++i];
            portnumber = atoi(argv[++i]);
        } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
            password = argv[++i];
+    } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
+           mode = 1;
+        batchfile = argv[++i];
+    } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
+           modeflags = modeflags | 1;
+    } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
+           modeflags = modeflags | 2;
        } else if (strcmp(argv[i], "--") == 0) {
            i++;
            break;
        } else if (strcmp(argv[i], "--") == 0) {
            i++;
            break;
@@ -1084,7 +1566,7 @@ int main(int argc, char *argv[])
     if (verbose && realhost != NULL)
        printf("Connected to %s\n", realhost);
 
     if (verbose && realhost != NULL)
        printf("Connected to %s\n", realhost);
 
-    do_sftp();
+    do_sftp(mode, modeflags, batchfile);
 
     if (back != NULL && back->socket() != NULL) {
        char ch;
 
     if (back != NULL && back->socket() != NULL) {
        char ch;