Revamp SSH authentication code so that user interaction is more
authorjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 30 Oct 2005 20:24:09 +0000 (20:24 +0000)
committerjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 30 Oct 2005 20:24:09 +0000 (20:24 +0000)
abstracted out; replace loops structured around a single interaction
per loop with less tortuous code (fixes: `ki-multiprompt-crash',
`ssh1-bad-passphrase-crash'; makes `ssh2-password-expiry' and
`proxy-password-prompt' easier).

The new interaction abstraction has a lot of fields that are unused in
the current code (things like window captions); this is groundwork for
`gui-auth'. However, ssh.c still writes directly to stderr; that may
want to be fixed.

In the GUI apps, user interaction is moved to terminal.c. This should
make it easier to fix things like UTF-8 username entry, although I
haven't attempted to do so. Also, control character filtering can be
tailored to be appropriate for individual front-ends; so far I don't
promise anything other than not having made it any worse.

I've tried to test this fairly exhaustively (although Mac stuff is
untested, as usual). It all seems to basically work, but I bet there
are new bugs. (One I know about is that you can no longer make the
PuTTY window go away with a ^D at the password prompt; this should be
fixed.)

git-svn-id: svn://svn.tartarus.org/sgt/putty@6437 cda61777-01e9-0310-a592-d414129be87e

18 files changed:
cmdgen.c
cmdline.c
mac/macterm.c
macosx/osxwin.m
misc.c
pscp.c
psftp.c
putty.h
ssh.c
terminal.c
unix/gtkwin.c
unix/uxcons.c
unix/uxplink.c
unix/uxsftp.c
windows/wincons.c
windows/window.c
windows/winplink.c
windows/winsftp.c

index 20c0d11..36da55e 100644 (file)
--- a/cmdgen.c
+++ b/cmdgen.c
  *    order to avoid depleting the test system's /dev/random
  *    unnecessarily.
  * 
- *  - Calls to console_get_line() are replaced with the diagnostic
- *    function below, so that I can run tests in an automated
- *    manner and provide their interactive passphrase inputs.
+ *  - Calls to console_get_userpass_input() are replaced with the
+ *    diagnostic function below, so that I can run tests in an
+ *    automated manner and provide their interactive passphrase
+ *    inputs.
  * 
  *  - main() is renamed to cmdgen_main(); at the bottom of the file
  *    I define another main() which calls the former repeatedly to
@@ -40,19 +41,23 @@ char *get_random_data(int len)
     memset(buf, 'x', len);
     return buf;
 }
-#define console_get_line console_get_line_diagnostic
+#define console_get_userpass_input console_get_userpass_input_diagnostic
 int nprompts, promptsgot;
 const char *prompts[3];
-int console_get_line(const char *prompt, char *str, int maxlen, int is_pw)
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
-    if (promptsgot < nprompts) {
-       assert(strlen(prompts[promptsgot]) < maxlen);
-       strcpy(str, prompts[promptsgot++]);
-       return TRUE;
-    } else {
-       promptsgot++;                  /* track number of requests anyway */
-       return FALSE;
+    size_t i;
+    int ret = 1;
+    for (i = 0; i < p->n_prompts; i++) {
+       if (promptsgot < nprompts) {
+           assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len);
+           strcpy(p->prompts[i]->result, prompts[promptsgot++]);
+       } else {
+           promptsgot++;           /* track number of requests anyway */
+           ret = 0;
+       }
     }
+    return ret;
 }
 #define main cmdgen_main
 #endif
@@ -670,11 +675,20 @@ int main(int argc, char **argv)
         * If so, ask for a passphrase.
         */
        if (encrypted && load_encrypted) {
-           passphrase = snewn(512, char);
-           if (!console_get_line("Enter passphrase to load key: ",
-                                 passphrase, 512, TRUE)) {
+           prompts_t *p = new_prompts(NULL);
+           int ret;
+           p->to_server = FALSE;
+           p->name = dupstr("SSH key passphrase");
+           add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE, 512);
+           ret = console_get_userpass_input(p, NULL, 0);
+           assert(ret >= 0);
+           if (!ret) {
+               free_prompts(p);
                perror("puttygen: unable to read passphrase");
                return 1;
+           } else {
+               passphrase = dupstr(p->prompts[0]->result);
+               free_prompts(p);
            }
        } else {
            passphrase = NULL;
@@ -785,31 +799,35 @@ int main(int argc, char **argv)
      * we have just generated a key.
      */
     if (change_passphrase || keytype != NOKEYGEN) {
-       char *passphrase2;
-
-       if (passphrase) {
-           memset(passphrase, 0, strlen(passphrase));
-           sfree(passphrase);
-       }
+       prompts_t *p = new_prompts(NULL);
+       int ret;
 
-       passphrase = snewn(512, char);
-       passphrase2 = snewn(512, char);
-       if (!console_get_line("Enter passphrase to save key: ",
-                             passphrase, 512, TRUE) ||
-           !console_get_line("Re-enter passphrase to verify: ",
-                             passphrase2, 512, TRUE)) {
+       p->to_server = FALSE;
+       p->name = dupstr("New SSH key passphrase");
+       add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE, 512);
+       add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE, 512);
+       ret = console_get_userpass_input(p, NULL, 0);
+       assert(ret >= 0);
+       if (!ret) {
+           free_prompts(p);
            perror("puttygen: unable to read new passphrase");
            return 1;
-       }
-       if (strcmp(passphrase, passphrase2)) {
-           fprintf(stderr, "puttygen: passphrases do not match\n");
-           return 1;
-       }
-       memset(passphrase2, 0, strlen(passphrase2));
-       sfree(passphrase2);
-       if (!*passphrase) {
-           sfree(passphrase);
-           passphrase = NULL;
+       } else {
+           if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) {
+               free_prompts(p);
+               fprintf(stderr, "puttygen: passphrases do not match\n");
+               return 1;
+           }
+           if (passphrase) {
+               memset(passphrase, 0, strlen(passphrase));
+               sfree(passphrase);
+           }
+           passphrase = dupstr(p->prompts[0]->result);
+           free_prompts(p);
+           if (!*passphrase) {
+               sfree(passphrase);
+               passphrase = NULL;
+           }
        }
     }
 
index fdd9584..112463c 100644 (file)
--- a/cmdline.c
+++ b/cmdline.c
@@ -63,23 +63,40 @@ void cmdline_cleanup(void)
     if (need_save) { cmdline_save_param(p, value, pri); return ret; } \
 } while (0)
 
-char *cmdline_password = NULL;
+static char *cmdline_password = NULL;
+
+/*
+ * Similar interface to get_userpass_input(), except that here a -1
+ * return means that we aren't capable of processing the prompt and
+ * someone else should do it.
+ */
+int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) {
 
-static int cmdline_get_line(const char *prompt, char *str,
-                            int maxlen, int is_pw)
-{
     static int tried_once = 0;
 
-    assert(is_pw && cmdline_password);
+    /*
+     * We only handle prompts which don't echo (which we assume to be
+     * passwords), and (currently) we only cope with a password prompt
+     * that comes in a prompt-set on its own.
+     */
+    if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) {
+       return -1;
+    }
 
-    if (tried_once) {
+    /*
+     * If we've tried once, return utter failure (no more passwords left
+     * to try).
+     */
+    if (tried_once)
        return 0;
-    } else {
-       strncpy(str, cmdline_password, maxlen);
-       str[maxlen - 1] = '\0';
-       tried_once = 1;
-       return 1;
-    }
+
+    strncpy(p->prompts[0]->result, cmdline_password,
+           p->prompts[0]->result_len);
+    p->prompts[0]->result[p->prompts[0]->result_len-1] = '\0';
+    memset(cmdline_password, 0, strlen(cmdline_password));
+    tried_once = 1;
+    return 1;
+
 }
 
 /*
@@ -272,8 +289,6 @@ int cmdline_process_param(char *p, char *value, int need_save, Config *cfg)
        RETURN(2);
        UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
        cmdline_password = value;
-       ssh_get_line = cmdline_get_line;
-       ssh_getline_pw_only = TRUE;
     }
 
     if (!strcmp(p, "-A")) {
index 443c0c8..68d8276 100644 (file)
@@ -1898,6 +1898,12 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len)
     return term_data(s->term, is_stderr, data, len);
 }
 
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    Session *s = p->frontend;
+    return term_get_userpass_input(s->term, p, in, inlen);
+}
+
 /*
  * Emacs magic:
  * Local Variables:
index e534c90..ba3a08e 100644 (file)
@@ -893,6 +893,13 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len)
     return [win fromBackend:data len:len isStderr:is_stderr];
 }
 
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    SessionWindow *win = (SessionWindow *)p->frontend;
+    Terminal *term = [win term];
+    return term_get_userpass_input(term, p, in, inlen);
+}
+
 void frontend_keypress(void *handle)
 {
     /* FIXME */
diff --git a/misc.c b/misc.c
index 9c8d203..37ac0a1 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -87,6 +87,46 @@ char ctrlparse(char *s, char **next)
     return c;
 }
 
+prompts_t *new_prompts(void *frontend)
+{
+    prompts_t *p = snew(prompts_t);
+    p->prompts = NULL;
+    p->n_prompts = 0;
+    p->frontend = frontend;
+    p->data = NULL;
+    p->to_server = TRUE; /* to be on the safe side */
+    p->name = p->instruction = NULL;
+    p->name_reqd = p->instr_reqd = FALSE;
+    return p;
+}
+void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len)
+{
+    prompt_t *pr = snew(prompt_t);
+    unsigned char *result = snewn(len, unsigned char);
+    pr->prompt = promptstr;
+    pr->echo = echo;
+    pr->result = result;
+    pr->result_len = len;
+    p->n_prompts++;
+    p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *);
+    p->prompts[p->n_prompts-1] = pr;
+}
+void free_prompts(prompts_t *p)
+{
+    size_t i;
+    for (i=0; i < p->n_prompts; i++) {
+       prompt_t *pr = p->prompts[i];
+       memset(pr->result, 0, pr->result_len); /* burn the evidence */
+       sfree(pr->result);
+       sfree(pr->prompt);
+       sfree(pr);
+    }
+    sfree(p->prompts);
+    sfree(p->name);
+    sfree(p->instruction);
+    sfree(p);
+}
+
 /* ----------------------------------------------------------------------
  * String handling routines.
  */
diff --git a/pscp.c b/pscp.c
index 19e5df4..575025f 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -220,6 +220,15 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
 
     return 0;
 }
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+    /*
+     * No "untrusted" output should get here (the way the code is
+     * currently, it's all diverted by FLAG_STDERR).
+     */
+    assert(!"Unexpected call to from_backend_untrusted()");
+    return 0; /* not reached */
+}
 static int ssh_scp_recv(unsigned char *buf, int len)
 {
     outptr = buf;
@@ -2204,7 +2213,6 @@ int psftp_main(int argc, char *argv[])
 #endif
        ;
     cmdline_tooltype = TOOLTYPE_FILETRANSFER;
-    ssh_get_line = &console_get_line;
     sk_init();
 
     /* Load Default Settings before doing anything else. */
diff --git a/psftp.c b/psftp.c
index 53b6616..2705d45 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -2523,6 +2523,15 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
 
     return 0;
 }
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+    /*
+     * No "untrusted" output should get here (the way the code is
+     * currently, it's all diverted by FLAG_STDERR).
+     */
+    assert(!"Unexpected call to from_backend_untrusted()");
+    return 0; /* not reached */
+}
 int sftp_recvdata(char *buf, int len)
 {
     outptr = (unsigned char *) buf;
@@ -2714,14 +2723,21 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
        cfg.username[sizeof(cfg.username) - 1] = '\0';
     }
     if (!cfg.username[0]) {
-       if (!console_get_line("login as: ",
-                             cfg.username, sizeof(cfg.username), FALSE)) {
+        /* FIXME: leave this to ssh.c? */
+        int ret;
+        prompts_t *p = new_prompts(NULL);
+        p->to_server = TRUE;
+        p->name = dupstr("SSH login name");
+        add_prompt(p, dupstr("login as: "), TRUE, lenof(cfg.username));
+        ret = get_userpass_input(p, NULL, 0);
+        assert(ret >= 0);
+        if (!ret) {
+            free_prompts(p);
            fprintf(stderr, "psftp: no username, aborting\n");
            cleanup_exit(1);
        } else {
-           int len = strlen(cfg.username);
-           if (cfg.username[len - 1] == '\n')
-               cfg.username[len - 1] = '\0';
+            memcpy(cfg.username, p->prompts[0]->result, lenof(cfg.username));
+            free_prompts(p);
        }
     }
 
@@ -2819,7 +2835,6 @@ int psftp_main(int argc, char *argv[])
 #endif
        ;
     cmdline_tooltype = TOOLTYPE_FILETRANSFER;
-    ssh_get_line = &console_get_line;
     sk_init();
 
     userhost = user = NULL;
diff --git a/putty.h b/putty.h
index b6284d5..1a2d7ec 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -612,6 +612,52 @@ GLOBAL int loaded_session;
 struct RSAKey;                        /* be a little careful of scope */
 
 /*
+ * Mechanism for getting text strings such as usernames and passwords
+ * from the front-end.
+ * The fields are mostly modelled after SSH's keyboard-interactive auth.
+ * FIXME We should probably mandate a character set/encoding (probably UTF-8).
+ *
+ * Since many of the pieces of text involved may be chosen by the server,
+ * the caller must take care to ensure that the server can't spoof locally-
+ * generated prompts such as key passphrase prompts. Some ground rules:
+ *  - If the front-end needs to truncate a string, it should lop off the
+ *    end.
+ *  - The front-end should filter out any dangerous characters and
+ *    generally not trust the strings. (But \n is required to behave
+ *    vaguely sensibly, at least in `instruction', and ideally in
+ *    `prompt[]' too.)
+ */
+typedef struct {
+    char *prompt;
+    int echo;
+    char *result;      /* allocated/freed by caller */
+    size_t result_len;
+} prompt_t;
+typedef struct {
+    /*
+     * Indicates whether the information entered is to be used locally
+     * (for instance a key passphrase prompt), or is destined for the wire.
+     * This is a hint only; the front-end is at liberty not to use this
+     * information (so the caller should ensure that the supplied text is
+     * sufficient).
+     */
+    int to_server;
+    char *name;                /* Short description, perhaps for dialog box title */
+    int name_reqd;     /* Display of `name' required or optional? */
+    char *instruction; /* Long description, maybe with embedded newlines */
+    int instr_reqd;    /* Display of `instruction' required or optional? */
+    size_t n_prompts;
+    prompt_t **prompts;
+    void *frontend;
+    void *data;                /* slot for housekeeping data, managed by
+                        * get_userpass_input(); initially NULL */
+} prompts_t;
+prompts_t *new_prompts(void *frontend);
+void add_prompt(prompts_t *p, char *promptstr, int echo, size_t len);
+/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
+void free_prompts(prompts_t *p);
+
+/*
  * Exports from the front end.
  */
 void request_resize(void *frontend, int, int);
@@ -652,10 +698,17 @@ void ldisc_update(void *frontend, int echo, int edit);
  * shutdown. */
 void update_specials_menu(void *frontend);
 int from_backend(void *frontend, int is_stderr, const char *data, int len);
+int from_backend_untrusted(void *frontend, const char *data, int len);
 void notify_remote_exit(void *frontend);
 /* Get a sensible value for a tty mode. NULL return = don't set.
  * Otherwise, returned value should be freed by caller. */
 char *get_ttymode(void *frontend, const char *mode);
+/*
+ * >0 = `got all results, carry on'
+ * 0  = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?)
+ * <0 = `please call back later with more in/inlen'
+ */
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
 #define OPTIMISE_IS_SCROLL 1
 
 void set_iconic(void *frontend, int iconic);
@@ -745,12 +798,15 @@ void term_copyall(Terminal *);
 void term_reconfig(Terminal *, Config *);
 void term_seen_key_event(Terminal *); 
 int term_data(Terminal *, int is_stderr, const char *data, int len);
+int term_data_untrusted(Terminal *, const char *data, int len);
 void term_provide_resize_fn(Terminal *term,
                            void (*resize_fn)(void *, int, int),
                            void *resize_ctx);
 void term_provide_logctx(Terminal *term, void *logctx);
 void term_set_focus(Terminal *term, int has_focus);
 char *term_get_ttymode(Terminal *term, const char *mode);
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+                           unsigned char *in, int inlen);
 
 /*
  * Exports from logging.c.
@@ -800,14 +856,8 @@ extern Backend rlogin_backend;
 extern Backend telnet_backend;
 
 /*
- * Exports from ssh.c. (NB the getline variables have to be GLOBAL
- * so that PuTTYtel will still compile - otherwise it would depend
- * on ssh.c.)
+ * Exports from ssh.c.
  */
-
-GLOBAL int (*ssh_get_line) (const char *prompt, char *str, int maxlen,
-                           int is_pw);
-GLOBAL int ssh_getline_pw_only;
 extern Backend ssh_backend;
 
 /*
@@ -952,11 +1002,11 @@ int askappend(void *frontend, Filename filename,
              void (*callback)(void *ctx, int result), void *ctx);
 
 /*
- * Exports from console.c (that aren't equivalents to things in
- * windlg.c).
+ * Exports from console frontends (wincons.c, uxcons.c)
+ * that aren't equivalents to things in windlg.c et al.
  */
 extern int console_batch_mode;
-int console_get_line(const char *prompt, char *str, int maxlen, int is_pw);
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
 void console_provide_logctx(void *logctx);
 int is_interactive(void);
 
@@ -980,7 +1030,7 @@ void printer_finish_job(printer_job *);
 int cmdline_process_param(char *, char *, int, Config *);
 void cmdline_run_saved(Config *);
 void cmdline_cleanup(void);
-extern char *cmdline_password;
+int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen);
 #define TOOLTYPE_FILETRANSFER 1
 #define TOOLTYPE_NONNETWORK 2
 extern int cmdline_tooltype;
diff --git a/ssh.c b/ssh.c
index 5493191..45ecf26 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -459,6 +459,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 #define OUR_V2_WINSIZE 16384
 #define OUR_V2_MAXPKT 0x4000UL
 
+/* Maximum length of passwords/passphrases (arbitrary) */
+#define SSH_MAX_PASSWORD_LEN 100
+
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 
 const static struct ssh_mac *macs[] = {
@@ -752,13 +755,6 @@ struct ssh_tag {
     int fallback_cmd;
 
     bufchain banner;   /* accumulates banners during do_ssh2_authconn */
-    /*
-     * Used for username and password input.
-     */
-    char *userpass_input_buffer;
-    int userpass_input_buflen;
-    int userpass_input_bufpos;
-    int userpass_input_echo;
 
     int pkt_ctx;
 
@@ -1046,27 +1042,28 @@ static int alloc_channel_id(Ssh ssh)
     return low + 1 + CHANNEL_NUMBER_OFFSET;
 }
 
+static void c_write_stderr(int trusted, const char *buf, int len)
+{
+    int i;
+    for (i = 0; i < len; i++)
+       if (buf[i] != '\r' && (trusted || buf[i] & 0x60))
+           fputc(buf[i], stderr);
+}
+
 static void c_write(Ssh ssh, const char *buf, int len)
 {
-    if ((flags & FLAG_STDERR)) {
-       int i;
-       for (i = 0; i < len; i++)
-           if (buf[i] != '\r')
-               fputc(buf[i], stderr);
-       return;
-    }
-    from_backend(ssh->frontend, 1, buf, len);
+    if (flags & FLAG_STDERR)
+       c_write_stderr(1, buf, len);
+    else
+       from_backend(ssh->frontend, 1, buf, len);
 }
 
 static void c_write_untrusted(Ssh ssh, const char *buf, int len)
 {
-    int i;
-    for (i = 0; i < len; i++) {
-       if (buf[i] == '\n')
-           c_write(ssh, "\r\n", 2);
-       else if ((buf[i] & 0x60) || (buf[i] == '\r'))
-           c_write(ssh, buf + i, 1);
-    }
+    if (flags & FLAG_STDERR)
+       c_write_stderr(0, buf, len);
+    else
+       from_backend_untrusted(ssh->frontend, buf, len);
 }
 
 static void c_write_str(Ssh ssh, const char *buf)
@@ -2751,78 +2748,6 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
     }
 }
 
-/*
- * Username and password input, abstracted off into routines
- * reusable in several places - even between SSH-1 and SSH-2.
- */
-
-/* Set up a username or password input loop on a given buffer. */
-static void setup_userpass_input(Ssh ssh, char *buffer, int buflen, int echo)
-{
-    ssh->userpass_input_buffer = buffer;
-    ssh->userpass_input_buflen = buflen;
-    ssh->userpass_input_bufpos = 0;
-    ssh->userpass_input_echo = echo;
-}
-
-/*
- * Process some terminal data in the course of username/password
- * input. Returns >0 for success (line of input returned in
- * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0
- * for inconclusive (keep waiting for more input please).
- */
-static int process_userpass_input(Ssh ssh, unsigned char *in, int inlen)
-{
-    char c;
-
-    while (inlen--) {
-       switch (c = *in++) {
-         case 10:
-         case 13:
-           ssh->userpass_input_buffer[ssh->userpass_input_bufpos] = 0;
-           ssh->userpass_input_buffer[ssh->userpass_input_buflen-1] = 0;
-           return +1;
-           break;
-         case 8:
-         case 127:
-           if (ssh->userpass_input_bufpos > 0) {
-               if (ssh->userpass_input_echo)
-                   c_write_str(ssh, "\b \b");
-               ssh->userpass_input_bufpos--;
-           }
-           break;
-         case 21:
-         case 27:
-           while (ssh->userpass_input_bufpos > 0) {
-               if (ssh->userpass_input_echo)
-                   c_write_str(ssh, "\b \b");
-               ssh->userpass_input_bufpos--;
-           }
-           break;
-         case 3:
-         case 4:
-           return -1;
-           break;
-         default:
-           /*
-            * This simplistic check for printability is disabled
-            * when we're doing password input, because some people
-            * have control characters in their passwords.o
-            */
-           if ((!ssh->userpass_input_echo ||
-                (c >= ' ' && c <= '~') ||
-                ((unsigned char) c >= 160))
-               && ssh->userpass_input_bufpos < ssh->userpass_input_buflen-1) {
-               ssh->userpass_input_buffer[ssh->userpass_input_bufpos++] = c;
-               if (ssh->userpass_input_echo)
-                   c_write(ssh, &c, 1);
-           }
-           break;
-       }
-    }
-    return 0;
-}
-
 static void ssh_agent_callback(void *sshv, void *reply, int replylen)
 {
     Ssh ssh = (Ssh) sshv;
@@ -2935,9 +2860,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        char username[100];
        void *publickey_blob;
        int publickey_bloblen;
-       char password[100];
-       char prompt[200];
-       int pos;
+       char *publickey_comment;
+       int publickey_encrypted;
+       prompts_t *cur_prompt;
        char c;
        int pwpkt_type;
        unsigned char request[5], *response, *p;
@@ -3199,33 +3124,33 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 
     logevent("Successfully started encryption");
 
-    fflush(stdout);
+    fflush(stdout); /* FIXME eh? */
     {
        if (!*ssh->cfg.username) {
-           if (ssh_get_line && !ssh_getline_pw_only) {
-               if (!ssh_get_line("login as: ",
-                                 s->username, sizeof(s->username), FALSE)) {
-                   /*
-                    * get_line failed to get a username.
-                    * Terminate.
-                    */
-                   ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
-                   crStop(1);
-               }
-           } else {
-               int ret;               /* need not be kept over crReturn */
-               c_write_str(ssh, "login as: ");
+           int ret; /* need not be kept over crReturn */
+           s->cur_prompt = new_prompts(ssh->frontend);
+           s->cur_prompt->to_server = TRUE;
+           s->cur_prompt->name = dupstr("SSH login name");
+           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE,
+                      lenof(s->username)); 
+           ret = get_userpass_input(s->cur_prompt, NULL, 0);
+           while (ret < 0) {
                ssh->send_ok = 1;
-
-               setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
-               do {
-                   crWaitUntil(!pktin);
-                   ret = process_userpass_input(ssh, in, inlen);
-               } while (ret == 0);
-               if (ret < 0)
-                   cleanup_exit(0);
-               c_write_str(ssh, "\r\n");
+               crWaitUntil(!pktin);
+               ret = get_userpass_input(s->cur_prompt, in, inlen);
+               ssh->send_ok = 0;
+           }
+           if (!ret) {
+               /*
+                * Failed to get a username. Terminate.
+                */
+               free_prompts(s->cur_prompt);
+               ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
+               crStop(0);
            }
+           memcpy(s->username, s->cur_prompt->prompts[0]->result,
+                  lenof(s->username));
+           free_prompts(s->cur_prompt);
        } else {
            strncpy(s->username, ssh->cfg.username, sizeof(s->username));
            s->username[sizeof(s->username)-1] = '\0';
@@ -3233,14 +3158,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 
        send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END);
        {
-           char userlog[22 + sizeof(s->username)];
-           sprintf(userlog, "Sent username \"%s\"", s->username);
+           char *userlog = dupprintf("Sent username \"%s\"", s->username);
            logevent(userlog);
            if (flags & FLAG_INTERACTIVE &&
                (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
-               strcat(userlog, "\r\n");
                c_write_str(ssh, userlog);
+               c_write_str(ssh, "\r\n");
            }
+           sfree(userlog);
        }
     }
 
@@ -3253,12 +3178,44 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        s->tried_publickey = s->tried_agent = 0;
     }
     s->tis_auth_refused = s->ccard_auth_refused = 0;
-    /* Load the public half of ssh->cfg.keyfile so we notice if it's in Pageant */
+    /*
+     * Load the public half of any configured keyfile for later use.
+     */
     if (!filename_is_null(ssh->cfg.keyfile)) {
-       if (!rsakey_pubblob(&ssh->cfg.keyfile,
-                           &s->publickey_blob, &s->publickey_bloblen,
-                           NULL, NULL))
+       int keytype;
+       logeventf(ssh, "Reading private key file \"%.150s\"",
+                 filename_to_str(&ssh->cfg.keyfile));
+       keytype = key_type(&ssh->cfg.keyfile);
+       if (keytype == SSH_KEYTYPE_SSH1) {
+           const char *error;
+           if (rsakey_pubblob(&ssh->cfg.keyfile,
+                              &s->publickey_blob, &s->publickey_bloblen,
+                              &s->publickey_comment, &error)) {
+               s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.keyfile,
+                                                         NULL);
+           } else {
+               char *msgbuf;
+               logeventf(ssh, "Unable to load private key (%s)", error);
+               msgbuf = dupprintf("Unable to load private key file "
+                                  "\"%.150s\" (%s)\r\n",
+                                  filename_to_str(&ssh->cfg.keyfile),
+                                  error);
+               c_write_str(ssh, msgbuf);
+               sfree(msgbuf);
+               s->publickey_blob = NULL;
+           }
+       } else {
+           char *msgbuf;
+           logeventf(ssh, "Unable to use this key file (%s)",
+                     key_type_to_str(keytype));
+           msgbuf = dupprintf("Unable to use key file \"%.150s\""
+                              " (%s)\r\n",
+                              filename_to_str(&ssh->cfg.keyfile),
+                              key_type_to_str(keytype));
+           c_write_str(ssh, msgbuf);
+           sfree(msgbuf);
            s->publickey_blob = NULL;
+       }
     } else
        s->publickey_blob = NULL;
 
@@ -3432,8 +3389,148 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            if (s->authed)
                break;
        }
-       if (!filename_is_null(ssh->cfg.keyfile) && !s->tried_publickey)
-           s->pwpkt_type = SSH1_CMSG_AUTH_RSA;
+       if (s->publickey_blob && !s->tried_publickey) {
+           /*
+            * Try public key authentication with the specified
+            * key file.
+            */
+           int got_passphrase; /* need not be kept over crReturn */
+           if (flags & FLAG_VERBOSE)
+               c_write_str(ssh, "Trying public key authentication.\r\n");
+           logeventf(ssh, "Trying public key \"%s\"",
+                     filename_to_str(&ssh->cfg.keyfile));
+           s->tried_publickey = 1;
+           got_passphrase = FALSE;
+           while (!got_passphrase) {
+               /*
+                * Get a passphrase, if necessary.
+                */
+               char *passphrase = NULL;    /* only written after crReturn */
+               const char *error;
+               if (!s->publickey_encrypted) {
+                   if (flags & FLAG_VERBOSE)
+                       c_write_str(ssh, "No passphrase required.\r\n");
+                   passphrase = NULL;
+               } else {
+                   int ret; /* need not be kept over crReturn */
+                   s->cur_prompt = new_prompts(ssh->frontend);
+                   s->cur_prompt->to_server = FALSE;
+                   s->cur_prompt->name = dupstr("SSH key passphrase");
+                   add_prompt(s->cur_prompt,
+                              dupprintf("Passphrase for key \"%.100s\": ",
+                                        s->publickey_comment),
+                              FALSE, SSH_MAX_PASSWORD_LEN);
+                   ret = get_userpass_input(s->cur_prompt, NULL, 0);
+                   while (ret < 0) {
+                       ssh->send_ok = 1;
+                       crWaitUntil(!pktin);
+                       ret = get_userpass_input(s->cur_prompt, in, inlen);
+                       ssh->send_ok = 0;
+                   }
+                   if (!ret) {
+                       /* Failed to get a passphrase. Terminate. */
+                       free_prompts(s->cur_prompt);
+                       ssh_disconnect(ssh, NULL, "Unable to authenticate",
+                                      0, TRUE);
+                       crStop(0);
+                   }
+                   passphrase = dupstr(s->cur_prompt->prompts[0]->result);
+                   free_prompts(s->cur_prompt);
+               }
+               /*
+                * Try decrypting key with passphrase.
+                */
+               ret = loadrsakey(&ssh->cfg.keyfile, &s->key, passphrase,
+                                &error);
+               if (passphrase) {
+                   memset(passphrase, 0, strlen(passphrase));
+                   sfree(passphrase);
+               }
+               if (ret == 1) {
+                   /* Correct passphrase. */
+                   got_passphrase = TRUE;
+               } else if (ret == 0) {
+                   c_write_str(ssh, "Couldn't load private key from ");
+                   c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile));
+                   c_write_str(ssh, " (");
+                   c_write_str(ssh, error);
+                   c_write_str(ssh, ").\r\n");
+                   got_passphrase = FALSE;
+                   break;             /* go and try something else */
+               } else if (ret == -1) {
+                   c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */
+                   s->tried_publickey = 0;
+                   got_passphrase = FALSE;
+                   /* and try again */
+               }
+           }
+
+           if (got_passphrase) {
+
+               /*
+                * Send a public key attempt.
+                */
+               send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+                           PKT_BIGNUM, s->key.modulus, PKT_END);
+
+               crWaitUntil(pktin);
+               if (pktin->type == SSH1_SMSG_FAILURE) {
+                   c_write_str(ssh, "Server refused our public key.\r\n");
+                   continue;          /* go and try password */
+               }
+               if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                   bombout(("Bizarre response to offer of public key"));
+                   crStop(0);
+               }
+
+               {
+                   int i;
+                   unsigned char buffer[32];
+                   Bignum challenge, response;
+
+                   if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) {
+                       bombout(("Server's RSA challenge was badly formatted"));
+                       crStop(0);
+                   }
+                   response = rsadecrypt(challenge, &s->key);
+                   freebn(s->key.private_exponent);/* burn the evidence */
+
+                   for (i = 0; i < 32; i++) {
+                       buffer[i] = bignum_byte(response, 31 - i);
+                   }
+
+                   MD5Init(&md5c);
+                   MD5Update(&md5c, buffer, 32);
+                   MD5Update(&md5c, s->session_id, 16);
+                   MD5Final(buffer, &md5c);
+
+                   send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+                               PKT_DATA, buffer, 16, PKT_END);
+
+                   freebn(challenge);
+                   freebn(response);
+               }
+
+               crWaitUntil(pktin);
+               if (pktin->type == SSH1_SMSG_FAILURE) {
+                   if (flags & FLAG_VERBOSE)
+                       c_write_str(ssh, "Failed to authenticate with"
+                                   " our public key.\r\n");
+                   continue;          /* go and try password */
+               } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+                   bombout(("Bizarre response to RSA authentication response"));
+                   crStop(0);
+               }
+
+               break;                 /* we're through! */
+           }
+
+       }
+
+       /*
+        * Otherwise, try various forms of password-like authentication.
+        */
+       s->cur_prompt = new_prompts(ssh->frontend);
 
        if (ssh->cfg.try_tis_auth &&
            (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
@@ -3451,23 +3548,31 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            } else {
                char *challenge;
                int challengelen;
+               char *instr_suf, *prompt;
 
                ssh_pkt_getstring(pktin, &challenge, &challengelen);
                if (!challenge) {
                    bombout(("TIS challenge packet was badly formed"));
                    crStop(0);
                }
-               c_write_str(ssh, "Using TIS authentication.\r\n");
                logevent("Received TIS challenge");
-               if (challengelen > sizeof(s->prompt) - 1)
-                   challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
-               memcpy(s->prompt, challenge, challengelen);
+               s->cur_prompt->to_server = TRUE;
+               s->cur_prompt->name = dupstr("SSH TIS authentication");
                /* Prompt heuristic comes from OpenSSH */
-               strncpy(s->prompt + challengelen,
-                       memchr(s->prompt, '\n', challengelen) ?
-                       "": "\r\nResponse: ",
-                       (sizeof s->prompt) - challengelen);
-               s->prompt[(sizeof s->prompt) - 1] = '\0';
+               if (memchr(challenge, '\n', challengelen)) {
+                   instr_suf = dupstr("");
+                   prompt = dupprintf("%.*s", challengelen, challenge);
+               } else {
+                   instr_suf = dupprintf("%.*s", challengelen, challenge);
+                   prompt = dupstr("Response: ");
+               }
+               s->cur_prompt->instruction =
+                   dupprintf("Using TIS authentication.%s%s",
+                             (*instr_suf) ? "\n" : "",
+                             instr_suf);
+               s->cur_prompt->instr_reqd = TRUE;
+               add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+               sfree(instr_suf);
            }
        }
        if (ssh->cfg.try_tis_auth &&
@@ -3485,53 +3590,40 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            } else {
                char *challenge;
                int challengelen;
+               char *instr_suf, *prompt;
 
                ssh_pkt_getstring(pktin, &challenge, &challengelen);
                if (!challenge) {
                    bombout(("CryptoCard challenge packet was badly formed"));
                    crStop(0);
                }
-               c_write_str(ssh, "Using CryptoCard authentication.\r\n");
                logevent("Received CryptoCard challenge");
-               if (challengelen > sizeof(s->prompt) - 1)
-                   challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
-               memcpy(s->prompt, challenge, challengelen);
-               strncpy(s->prompt + challengelen,
-                       memchr(s->prompt, '\n', challengelen) ?
-                       "" : "\r\nResponse: ",
-                       sizeof(s->prompt) - challengelen);
-               s->prompt[sizeof(s->prompt) - 1] = '\0';
+               s->cur_prompt->to_server = TRUE;
+               s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
+               s->cur_prompt->name_reqd = FALSE;
+               /* Prompt heuristic comes from OpenSSH */
+               if (memchr(challenge, '\n', challengelen)) {
+                   instr_suf = dupstr("");
+                   prompt = dupprintf("%.*s", challengelen, challenge);
+               } else {
+                   instr_suf = dupprintf("%.*s", challengelen, challenge);
+                   prompt = dupstr("Response: ");
+               }
+               s->cur_prompt->instruction =
+                   dupprintf("Using CryptoCard authentication.%s%s",
+                             (*instr_suf) ? "\n" : "",
+                             instr_suf);
+               s->cur_prompt->instr_reqd = TRUE;
+               add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+               sfree(instr_suf);
            }
        }
        if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
-           sprintf(s->prompt, "%.90s@%.90s's password: ",
-                   s->username, ssh->savedhost);
-       }
-       if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
-           char *comment = NULL;
-           int type;
-           if (flags & FLAG_VERBOSE)
-               c_write_str(ssh, "Trying public key authentication.\r\n");
-           logeventf(ssh, "Trying public key \"%s\"",
-                     filename_to_str(&ssh->cfg.keyfile));
-           type = key_type(&ssh->cfg.keyfile);
-           if (type != SSH_KEYTYPE_SSH1) {
-               char *msg = dupprintf("Key is of wrong type (%s)",
-                                     key_type_to_str(type));
-               logevent(msg);
-               c_write_str(ssh, msg);
-               c_write_str(ssh, "\r\n");
-               sfree(msg);
-               s->tried_publickey = 1;
-               continue;
-           }
-           if (!rsakey_encrypted(&ssh->cfg.keyfile, &comment)) {
-               if (flags & FLAG_VERBOSE)
-                   c_write_str(ssh, "No passphrase required.\r\n");
-               goto tryauth;
-           }
-           sprintf(s->prompt, "Passphrase for key \"%.100s\": ", comment);
-           sfree(comment);
+           s->cur_prompt->to_server = TRUE;
+           s->cur_prompt->name = dupstr("SSH password");
+           add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ",
+                                               s->username, ssh->savedhost),
+                      FALSE, SSH_MAX_PASSWORD_LEN);
        }
 
        /*
@@ -3539,245 +3631,155 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
         * or CryptoCard exchange if we're doing TIS or CryptoCard
         * authentication.
         */
-       if (ssh_get_line) {
-           if (!ssh_get_line(s->prompt, s->password,
-                             sizeof(s->password), TRUE)) {
+       {
+           int ret; /* need not be kept over crReturn */
+           ret = get_userpass_input(s->cur_prompt, NULL, 0);
+           while (ret < 0) {
+               ssh->send_ok = 1;
+               crWaitUntil(!pktin);
+               ret = get_userpass_input(s->cur_prompt, in, inlen);
+               ssh->send_ok = 0;
+           }
+           if (!ret) {
                /*
-                * get_line failed to get a password (for example
+                * Failed to get a password (for example
                 * because one was supplied on the command line
                 * which has already failed to work). Terminate.
                 */
-               ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE);
-               crStop(1);
+               free_prompts(s->cur_prompt);
+               ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE);
+               crStop(0);
            }
-       } else {
-           /* Prompt may have come from server. We've munged it a bit, so
-            * we know it to be zero-terminated at least once. */
-           int ret;                   /* need not be saved over crReturn */
-           c_write_untrusted(ssh, s->prompt, strlen(s->prompt));
-           s->pos = 0;
-
-           setup_userpass_input(ssh, s->password, sizeof(s->password), 0);
-           do {
-               crWaitUntil(!pktin);
-               ret = process_userpass_input(ssh, in, inlen);
-           } while (ret == 0);
-           if (ret < 0)
-               cleanup_exit(0);
-           c_write_str(ssh, "\r\n");
        }
 
-      tryauth:
-       if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
-           /*
-            * Try public key authentication with the specified
-            * key file.
-            */
-           s->tried_publickey = 1;
-           
-           {
-               const char *error = NULL;
-               int ret = loadrsakey(&ssh->cfg.keyfile, &s->key, s->password,
-                                    &error);
-               if (ret == 0) {
-                   c_write_str(ssh, "Couldn't load private key from ");
-                   c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile));
-                   c_write_str(ssh, " (");
-                   c_write_str(ssh, error);
-                   c_write_str(ssh, ").\r\n");
-                   continue;          /* go and try password */
-               }
-               if (ret == -1) {
-                   c_write_str(ssh, "Wrong passphrase.\r\n");
-                   s->tried_publickey = 0;
-                   continue;          /* try again */
-               }
-           }
-
+       if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
            /*
-            * Send a public key attempt.
+            * Defence against traffic analysis: we send a
+            * whole bunch of packets containing strings of
+            * different lengths. One of these strings is the
+            * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+            * The others are all random data in
+            * SSH1_MSG_IGNORE packets. This way a passive
+            * listener can't tell which is the password, and
+            * hence can't deduce the password length.
+            * 
+            * Anybody with a password length greater than 16
+            * bytes is going to have enough entropy in their
+            * password that a listener won't find it _that_
+            * much help to know how long it is. So what we'll
+            * do is:
+            * 
+            *  - if password length < 16, we send 15 packets
+            *    containing string lengths 1 through 15
+            * 
+            *  - otherwise, we let N be the nearest multiple
+            *    of 8 below the password length, and send 8
+            *    packets containing string lengths N through
+            *    N+7. This won't obscure the order of
+            *    magnitude of the password length, but it will
+            *    introduce a bit of extra uncertainty.
+            * 
+            * A few servers (the old 1.2.18 through 1.2.22)
+            * can't deal with SSH1_MSG_IGNORE. For these
+            * servers, we need an alternative defence. We make
+            * use of the fact that the password is interpreted
+            * as a C string: so we can append a NUL, then some
+            * random data.
+            * 
+            * One server (a Cisco one) can deal with neither
+            * SSH1_MSG_IGNORE _nor_ a padded password string.
+            * For this server we are left with no defences
+            * against password length sniffing.
             */
-           send_packet(ssh, SSH1_CMSG_AUTH_RSA,
-                       PKT_BIGNUM, s->key.modulus, PKT_END);
-
-           crWaitUntil(pktin);
-           if (pktin->type == SSH1_SMSG_FAILURE) {
-               c_write_str(ssh, "Server refused our public key.\r\n");
-               continue;              /* go and try password */
-           }
-           if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
-               bombout(("Bizarre response to offer of public key"));
-               crStop(0);
-           }
-
-           {
-               int i;
-               unsigned char buffer[32];
-               Bignum challenge, response;
-
-               if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) {
-                   bombout(("Server's RSA challenge was badly formatted"));
-                   crStop(0);
-               }
-               response = rsadecrypt(challenge, &s->key);
-               freebn(s->key.private_exponent);/* burn the evidence */
-
-               for (i = 0; i < 32; i++) {
-                   buffer[i] = bignum_byte(response, 31 - i);
-               }
-
-               MD5Init(&md5c);
-               MD5Update(&md5c, buffer, 32);
-               MD5Update(&md5c, s->session_id, 16);
-               MD5Final(buffer, &md5c);
-
-               send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
-                           PKT_DATA, buffer, 16, PKT_END);
-
-               freebn(challenge);
-               freebn(response);
-           }
-
-           crWaitUntil(pktin);
-           if (pktin->type == SSH1_SMSG_FAILURE) {
-               if (flags & FLAG_VERBOSE)
-                   c_write_str(ssh, "Failed to authenticate with"
-                               " our public key.\r\n");
-               continue;              /* go and try password */
-           } else if (pktin->type != SSH1_SMSG_SUCCESS) {
-               bombout(("Bizarre response to RSA authentication response"));
-               crStop(0);
-           }
-
-           break;                     /* we're through! */
-       } else {
-           if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+           if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
                /*
-                * Defence against traffic analysis: we send a
-                * whole bunch of packets containing strings of
-                * different lengths. One of these strings is the
-                * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
-                * The others are all random data in
-                * SSH1_MSG_IGNORE packets. This way a passive
-                * listener can't tell which is the password, and
-                * hence can't deduce the password length.
-                * 
-                * Anybody with a password length greater than 16
-                * bytes is going to have enough entropy in their
-                * password that a listener won't find it _that_
-                * much help to know how long it is. So what we'll
-                * do is:
-                * 
-                *  - if password length < 16, we send 15 packets
-                *    containing string lengths 1 through 15
-                * 
-                *  - otherwise, we let N be the nearest multiple
-                *    of 8 below the password length, and send 8
-                *    packets containing string lengths N through
-                *    N+7. This won't obscure the order of
-                *    magnitude of the password length, but it will
-                *    introduce a bit of extra uncertainty.
-                * 
-                * A few servers (the old 1.2.18 through 1.2.22)
-                * can't deal with SSH1_MSG_IGNORE. For these
-                * servers, we need an alternative defence. We make
-                * use of the fact that the password is interpreted
-                * as a C string: so we can append a NUL, then some
-                * random data.
-                * 
-                * One server (a Cisco one) can deal with neither
-                * SSH1_MSG_IGNORE _nor_ a padded password string.
-                * For this server we are left with no defences
-                * against password length sniffing.
+                * The server can deal with SSH1_MSG_IGNORE, so
+                * we can use the primary defence.
                 */
-               if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
-                   /*
-                    * The server can deal with SSH1_MSG_IGNORE, so
-                    * we can use the primary defence.
-                    */
-                   int bottom, top, pwlen, i;
-                   char *randomstr;
+               int bottom, top, pwlen, i;
+               char *randomstr;
 
-                   pwlen = strlen(s->password);
-                   if (pwlen < 16) {
-                       bottom = 0;    /* zero length passwords are OK! :-) */
-                       top = 15;
-                   } else {
-                       bottom = pwlen & ~7;
-                       top = bottom + 7;
-                   }
+               pwlen = strlen(s->cur_prompt->prompts[0]->result);
+               if (pwlen < 16) {
+                   bottom = 0;    /* zero length passwords are OK! :-) */
+                   top = 15;
+               } else {
+                   bottom = pwlen & ~7;
+                   top = bottom + 7;
+               }
 
-                   assert(pwlen >= bottom && pwlen <= top);
+               assert(pwlen >= bottom && pwlen <= top);
 
-                   randomstr = snewn(top + 1, char);
+               randomstr = snewn(top + 1, char);
 
-                   for (i = bottom; i <= top; i++) {
-                       if (i == pwlen) {
-                           defer_packet(ssh, s->pwpkt_type,
-                                        PKTT_PASSWORD, PKT_STR, s->password,
-                                        PKTT_OTHER, PKT_END);
-                       } else {
-                           for (j = 0; j < i; j++) {
-                               do {
-                                   randomstr[j] = random_byte();
-                               } while (randomstr[j] == '\0');
-                           }
-                           randomstr[i] = '\0';
-                           defer_packet(ssh, SSH1_MSG_IGNORE,
-                                        PKT_STR, randomstr, PKT_END);
+               for (i = bottom; i <= top; i++) {
+                   if (i == pwlen) {
+                       defer_packet(ssh, s->pwpkt_type,
+                                    PKTT_PASSWORD, PKT_STR,
+                                    s->cur_prompt->prompts[0]->result,
+                                    PKTT_OTHER, PKT_END);
+                   } else {
+                       for (j = 0; j < i; j++) {
+                           do {
+                               randomstr[j] = random_byte();
+                           } while (randomstr[j] == '\0');
                        }
+                       randomstr[i] = '\0';
+                       defer_packet(ssh, SSH1_MSG_IGNORE,
+                                    PKT_STR, randomstr, PKT_END);
                    }
-                   logevent("Sending password with camouflage packets");
-                   ssh_pkt_defersend(ssh);
-                   sfree(randomstr);
-               } 
-               else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
-                   /*
-                    * The server can't deal with SSH1_MSG_IGNORE
-                    * but can deal with padded passwords, so we
-                    * can use the secondary defence.
-                    */
-                   char string[64];
-                   char *ss;
-                   int len;
-
-                   len = strlen(s->password);
-                   if (len < sizeof(string)) {
-                       ss = string;
-                       strcpy(string, s->password);
-                       len++;         /* cover the zero byte */
-                       while (len < sizeof(string)) {
-                           string[len++] = (char) random_byte();
-                       }
-                   } else {
-                       ss = s->password;
+               }
+               logevent("Sending password with camouflage packets");
+               ssh_pkt_defersend(ssh);
+               sfree(randomstr);
+           } 
+           else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+               /*
+                * The server can't deal with SSH1_MSG_IGNORE
+                * but can deal with padded passwords, so we
+                * can use the secondary defence.
+                */
+               char string[64];
+               char *ss;
+               int len;
+
+               len = strlen(s->cur_prompt->prompts[0]->result);
+               if (len < sizeof(string)) {
+                   ss = string;
+                   strcpy(string, s->cur_prompt->prompts[0]->result);
+                   len++;             /* cover the zero byte */
+                   while (len < sizeof(string)) {
+                       string[len++] = (char) random_byte();
                    }
-                   logevent("Sending length-padded password");
-                   send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
-                               PKT_INT, len, PKT_DATA, ss, len,
-                               PKTT_OTHER, PKT_END);
                } else {
-                   /*
-                    * The server has _both_
-                    * BUG_CHOKES_ON_SSH1_IGNORE and
-                    * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
-                    * therefore nothing we can do.
-                    */
-                   int len;
-                   len = strlen(s->password);
-                   logevent("Sending unpadded password");
-                   send_packet(ssh, s->pwpkt_type,
-                               PKTT_PASSWORD, PKT_INT, len,
-                               PKT_DATA, s->password, len,
-                               PKTT_OTHER, PKT_END);
+                   ss = s->cur_prompt->prompts[0]->result;
                }
-           } else {
+               logevent("Sending length-padded password");
                send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
-                           PKT_STR, s->password, PKTT_OTHER, PKT_END);
+                           PKT_INT, len, PKT_DATA, ss, len,
+                           PKTT_OTHER, PKT_END);
+           } else {
+               /*
+                * The server has _both_
+                * BUG_CHOKES_ON_SSH1_IGNORE and
+                * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+                * therefore nothing we can do.
+                */
+               int len;
+               len = strlen(s->cur_prompt->prompts[0]->result);
+               logevent("Sending unpadded password");
+               send_packet(ssh, s->pwpkt_type,
+                           PKTT_PASSWORD, PKT_INT, len,
+                           PKT_DATA, s->cur_prompt->prompts[0]->result, len,
+                           PKTT_OTHER, PKT_END);
            }
+       } else {
+           send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
+                       PKT_STR, s->cur_prompt->prompts[0]->result,
+                       PKTT_OTHER, PKT_END);
        }
        logevent("Sent password");
-       memset(s->password, 0, strlen(s->password));
+       free_prompts(s->cur_prompt);
        crWaitUntil(pktin);
        if (pktin->type == SSH1_SMSG_FAILURE) {
            if (flags & FLAG_VERBOSE)
@@ -3789,6 +3791,12 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        }
     }
 
+    /* Clear up */
+    if (s->publickey_blob) {
+       sfree(s->publickey_blob);
+       sfree(s->publickey_comment);
+    }
+
     logevent("Authentication successful");
 
     crFinish(1);
@@ -6482,15 +6490,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int done_service_req;
        int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
        int tried_pubkey_config, tried_agent;
-       int kbd_inter_running, kbd_inter_refused;
+       int kbd_inter_refused;
        int we_are_in;
-       int num_prompts, curr_prompt, echo;
+       prompts_t *cur_prompt;
+       int num_prompts;
        char username[100];
        int got_username;
-       char pwprompt[512];
-       char password[100];
        void *publickey_blob;
        int publickey_bloblen;
+       int publickey_encrypted;
+       char *publickey_algorithm;
+       char *publickey_comment;
        unsigned char request[5], *response, *p;
        int responselen;
        int keyi, nkeys;
@@ -6537,6 +6547,58 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     }
 
     /*
+     * Misc one-time setup for authentication.
+     */
+    s->publickey_blob = NULL;
+    if (!s->we_are_in) {
+
+       /*
+        * Load the public half of any configured public key file
+        * for later use.
+        */
+       if (!filename_is_null(ssh->cfg.keyfile)) {
+           int keytype;
+           logeventf(ssh, "Reading private key file \"%.150s\"",
+                     filename_to_str(&ssh->cfg.keyfile));
+           keytype = key_type(&ssh->cfg.keyfile);
+           if (keytype == SSH_KEYTYPE_SSH2) {
+               const char *error;
+               s->publickey_blob =
+                   ssh2_userkey_loadpub(&ssh->cfg.keyfile,
+                                        &s->publickey_algorithm,
+                                        &s->publickey_bloblen, 
+                                        &s->publickey_comment, &error);
+               if (s->publickey_blob) {
+                   s->publickey_encrypted =
+                       ssh2_userkey_encrypted(&ssh->cfg.keyfile, NULL);
+               } else {
+                   char *msgbuf;
+                   logeventf(ssh, "Unable to load private key (%s)", 
+                             error);
+                   msgbuf = dupprintf("Unable to load private key file "
+                                      "\"%.150s\" (%s)\r\n",
+                                      filename_to_str(&ssh->cfg.keyfile),
+                                      error);
+                   c_write_str(ssh, msgbuf);
+                   sfree(msgbuf);
+               }
+           } else {
+               char *msgbuf;
+               logeventf(ssh, "Unable to use this key file (%s)",
+                         key_type_to_str(keytype));
+               msgbuf = dupprintf("Unable to use key file \"%.150s\""
+                                  " (%s)\r\n",
+                                  filename_to_str(&ssh->cfg.keyfile),
+                                  key_type_to_str(keytype));
+               c_write_str(ssh, msgbuf);
+               sfree(msgbuf);
+               s->publickey_blob = NULL;
+           }
+       }
+
+    }
+
+    /*
      * We repeat this whole loop, including the username prompt,
      * until we manage a successful authentication. If the user
      * types the wrong _password_, they can be sent back to the
@@ -6576,30 +6638,31 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * it again.
             */
        } else if (!*ssh->cfg.username) {
-           if (ssh_get_line && !ssh_getline_pw_only) {
-               if (!ssh_get_line("login as: ",
-                                 s->username, sizeof(s->username), FALSE)) {
-                   /*
-                    * get_line failed to get a username.
-                    * Terminate.
-                    */
-                   ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
-                   crStopV;
-               }
-           } else {
-               int ret;               /* need not be saved across crReturn */
-               c_write_str(ssh, "login as: ");
+           int ret; /* need not be kept over crReturn */
+           s->cur_prompt = new_prompts(ssh->frontend);
+           s->cur_prompt->to_server = TRUE;
+           s->cur_prompt->name = dupstr("SSH login name");
+           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE,
+                      lenof(s->username)); 
+           ret = get_userpass_input(s->cur_prompt, NULL, 0);
+           while (ret < 0) {
                ssh->send_ok = 1;
-               setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
-               do {
-                   crWaitUntilV(!pktin);
-                   ret = process_userpass_input(ssh, in, inlen);
-               } while (ret == 0);
-               if (ret < 0)
-                   cleanup_exit(0);
-               c_write_str(ssh, "\r\n");
+               crWaitUntilV(!pktin);
+               ret = get_userpass_input(s->cur_prompt, in, inlen);
+               ssh->send_ok = 0;
            }
-           s->username[strcspn(s->username, "\n\r")] = '\0';
+           if (!ret) {
+               /*
+                * get_userpass_input() failed to get a username.
+                * Terminate.
+                */
+               free_prompts(s->cur_prompt);
+               ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
+               crStopV;
+           }
+           memcpy(s->username, s->cur_prompt->prompts[0]->result,
+                  lenof(s->username));
+           free_prompts(s->cur_prompt);
        } else {
            char *stuff;
            strncpy(s->username, ssh->cfg.username, sizeof(s->username));
@@ -6630,32 +6693,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
        s->tried_pubkey_config = FALSE;
        s->tried_agent = FALSE;
-       s->kbd_inter_running = FALSE;
        s->kbd_inter_refused = FALSE;
-       /* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */
-       if (!filename_is_null(ssh->cfg.keyfile)) {
-           int keytype;
-           logeventf(ssh, "Reading private key file \"%.150s\"",
-                     filename_to_str(&ssh->cfg.keyfile));
-           keytype = key_type(&ssh->cfg.keyfile);
-           if (keytype == SSH_KEYTYPE_SSH2) {
-               s->publickey_blob =
-                   ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL,
-                                        &s->publickey_bloblen, NULL, NULL);
-           } else {
-               char *msgbuf;
-               logeventf(ssh, "Unable to use this key file (%s)",
-                         key_type_to_str(keytype));
-               msgbuf = dupprintf("Unable to use key file \"%.150s\""
-                                  " (%s)\r\n",
-                                  filename_to_str(&ssh->cfg.keyfile),
-                                  key_type_to_str(keytype));
-               c_write_str(ssh, msgbuf);
-               sfree(msgbuf);
-               s->publickey_blob = NULL;
-           }
-       } else
-           s->publickey_blob = NULL;
 
        while (1) {
            /*
@@ -6693,18 +6731,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                break;
            }
 
-           if (s->kbd_inter_running &&
-               pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
-               /*
-                * This is either a further set-of-prompts packet
-                * in keyboard-interactive authentication, or it's
-                * the same one and we came back here with `gotit'
-                * set. In the former case, we must reset the
-                * curr_prompt variable.
-                */
-               if (!s->gotit)
-                   s->curr_prompt = 0;
-           } else if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+           if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
                /* FIXME: perhaps we should support this? */
                bombout(("PASSWD_CHANGEREQ not yet supported"));
                crStopV;
@@ -6725,7 +6752,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                char *methods;
                int methlen;
                ssh_pkt_getstring(pktin, &methods, &methlen);
-               s->kbd_inter_running = FALSE;
                if (!ssh2_pkt_getbool(pktin)) {
                    /*
                     * We have received an unequivocal Access
@@ -6782,20 +6808,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    in_commasep_string("keyboard-interactive", methods, methlen);
            }
 
-           s->method = 0;
            ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-           s->need_pw = FALSE;
 
-           /*
-            * Most password/passphrase prompts will be
-            * non-echoing, so we set this to 0 by default.
-            * Exception is that some keyboard-interactive prompts
-            * can be echoing, in which case we'll set this to 1.
-            */
-           s->echo = 0;
+           if (s->can_pubkey && agent_exists() && !s->tried_agent) {
 
-           if (!s->method && s->can_pubkey &&
-               agent_exists() && !s->tried_agent) {
                /*
                 * Attempt public-key authentication using Pageant.
                 */
@@ -6960,234 +6976,127 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                        continue;
                }
                sfree(s->response);
-           }
 
-           if (!s->method && s->can_pubkey && s->publickey_blob
-               && !s->tried_pubkey_config) {
-               unsigned char *pub_blob;
-               char *algorithm, *comment;
-               int pub_blob_len;
+           } else if (s->can_pubkey && s->publickey_blob &&
+                      !s->tried_pubkey_config) {
 
-               s->tried_pubkey_config = TRUE;
+               struct ssh2_userkey *key;   /* not live over crReturn */
+               char *passphrase;           /* not live over crReturn */
 
                ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
 
+               s->tried_pubkey_config = TRUE;
+
                /*
                 * Try the public key supplied in the configuration.
                 *
                 * First, offer the public blob to see if the server is
                 * willing to accept it.
                 */
-               pub_blob =
-                   (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile,
-                                                         &algorithm,
-                                                         &pub_blob_len,
-                                                         NULL, NULL);
-               if (pub_blob) {
-                   s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
-                   ssh2_pkt_addstring(s->pktout, s->username);
-                   ssh2_pkt_addstring(s->pktout, "ssh-connection");    /* service requested */
-                   ssh2_pkt_addstring(s->pktout, "publickey"); /* method */
-                   ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */
-                   ssh2_pkt_addstring(s->pktout, algorithm);
-                   ssh2_pkt_addstring_start(s->pktout);
-                   ssh2_pkt_addstring_data(s->pktout, (char *)pub_blob,
-                                           pub_blob_len);
-                   ssh2_pkt_send(ssh, s->pktout);
-                   logevent("Offered public key");
-
-                   crWaitUntilV(pktin);
-                   if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
-                       s->gotit = TRUE;
-                       s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
-                       continue;      /* key refused; give up on it */
-                   }
-
-                   logevent("Offer of public key accepted");
-                   /*
-                    * Actually attempt a serious authentication using
-                    * the key.
-                    */
-                   if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) {
-                       sprintf(s->pwprompt,
-                               "Passphrase for key \"%.100s\": ",
-                               comment);
-                       s->need_pw = TRUE;
-                   } else {
-                       s->need_pw = FALSE;
-                   }
-                   if (flags & FLAG_VERBOSE) {
-                       c_write_str(ssh, "Authenticating with public key \"");
-                       c_write_str(ssh, comment);
-                       c_write_str(ssh, "\"\r\n");
-                   }
-                   s->method = AUTH_PUBLICKEY_FILE;
-               }
-           }
-
-           if (!s->method && s->can_keyb_inter && !s->kbd_inter_refused &&
-               !s->kbd_inter_running) {
-               s->method = AUTH_KEYBOARD_INTERACTIVE;
-               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-
-               ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-               ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
-
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(s->pktout, s->username);
-               ssh2_pkt_addstring(s->pktout, "ssh-connection");        /* service requested */
-               ssh2_pkt_addstring(s->pktout, "keyboard-interactive");  /* method */
-               ssh2_pkt_addstring(s->pktout, ""); /* lang */
-               ssh2_pkt_addstring(s->pktout, "");
+               ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                               /* service requested */
+               ssh2_pkt_addstring(s->pktout, "publickey");     /* method */
+               ssh2_pkt_addbool(s->pktout, FALSE);
+                                               /* no signature included */
+               ssh2_pkt_addstring(s->pktout, s->publickey_algorithm);
+               ssh2_pkt_addstring_start(s->pktout);
+               ssh2_pkt_addstring_data(s->pktout,
+                                       (char *)s->publickey_blob,
+                                       s->publickey_bloblen);
                ssh2_pkt_send(ssh, s->pktout);
+               logevent("Offered public key");
 
                crWaitUntilV(pktin);
-               if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
-                   if (pktin->type == SSH2_MSG_USERAUTH_FAILURE)
-                       s->gotit = TRUE;
-                   logevent("Keyboard-interactive authentication refused");
-                   s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
-                   s->kbd_inter_refused = TRUE; /* don't try it again */
-                   continue;
-               }
-
-               c_write_str(ssh, "Using keyboard-interactive authentication.\r\n");
-               s->kbd_inter_running = TRUE;
-               s->curr_prompt = 0;
-           }
-
-           if (s->kbd_inter_running) {
-               s->method = AUTH_KEYBOARD_INTERACTIVE;
-               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-
-               ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-               ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
-
-               if (s->curr_prompt == 0) {
-                   /*
-                    * We've got a fresh USERAUTH_INFO_REQUEST.
-                    * Display header data, and start going through
-                    * the prompts.
-                    */
-                   char *name, *inst, *lang;
-                   int name_len, inst_len, lang_len;
-
-                   ssh_pkt_getstring(pktin, &name, &name_len);
-                   ssh_pkt_getstring(pktin, &inst, &inst_len);
-                   ssh_pkt_getstring(pktin, &lang, &lang_len);
-                   if (name_len > 0) {
-                       c_write_untrusted(ssh, name, name_len);
-                       c_write_str(ssh, "\r\n");
-                   }
-                   if (inst_len > 0) {
-                       c_write_untrusted(ssh, inst, inst_len);
-                       c_write_str(ssh, "\r\n");
-                   }
-                   s->num_prompts = ssh_pkt_getuint32(pktin);
+               if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+                   /* Key refused. Give up. */
+                   s->gotit = TRUE; /* reconsider message next loop */
+                   s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+                   continue; /* process this new message */
                }
+               s->method = AUTH_PUBLICKEY_FILE;
+               logevent("Offer of public key accepted");
 
                /*
-                * If there are prompts remaining in the packet,
-                * display one and get a response.
+                * Actually attempt a serious authentication using
+                * the key.
                 */
-               if (s->curr_prompt < s->num_prompts) {
-                   char *prompt;
-                   int prompt_len;
-
-                   ssh_pkt_getstring(pktin, &prompt, &prompt_len);
-                   if (prompt_len > 0) {
-                       static const char trunc[] = "<prompt truncated>: ";
-                       static const int prlen = sizeof(s->pwprompt) -
-                                                lenof(trunc);
-                       if (prompt_len > prlen) {
-                           memcpy(s->pwprompt, prompt, prlen);
-                           strcpy(s->pwprompt + prlen, trunc);
-                       } else {
-                           memcpy(s->pwprompt, prompt, prompt_len);
-                           s->pwprompt[prompt_len] = '\0';
+               if (flags & FLAG_VERBOSE) {
+                   c_write_str(ssh, "Authenticating with public key \"");
+                   c_write_str(ssh, s->publickey_comment);
+                   c_write_str(ssh, "\"\r\n");
+               }
+               key = NULL;
+               while (!key) {
+                   const char *error;  /* not live over crReturn */
+                   if (s->publickey_encrypted) {
+                       /*
+                        * Get a passphrase from the user.
+                        */
+                       int ret; /* need not be kept over crReturn */
+                       s->cur_prompt = new_prompts(ssh->frontend);
+                       s->cur_prompt->to_server = FALSE;
+                       s->cur_prompt->name = dupstr("SSH key passphrase");
+                       add_prompt(s->cur_prompt,
+                                  dupprintf("Passphrase for key \"%.100s\": ",
+                                            s->publickey_comment),
+                                  FALSE, SSH_MAX_PASSWORD_LEN);
+                       ret = get_userpass_input(s->cur_prompt, NULL, 0);
+                       while (ret < 0) {
+                           ssh->send_ok = 1;
+                           crWaitUntilV(!pktin);
+                           ret = get_userpass_input(s->cur_prompt,
+                                                    in, inlen);
+                           ssh->send_ok = 0;
+                       }
+                       if (!ret) {
+                           /* Failed to get a passphrase. Terminate. */
+                           free_prompts(s->cur_prompt);
+                           ssh_disconnect(ssh, NULL,
+                                          "Unable to authenticate",
+                                          SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+                                          TRUE);
+                           crStopV;
                        }
+                       passphrase =
+                           dupstr(s->cur_prompt->prompts[0]->result);
+                       free_prompts(s->cur_prompt);
                    } else {
-                       strcpy(s->pwprompt,
-                              "<server failed to send prompt>: ");
+                       passphrase = NULL; /* no passphrase needed */
                    }
-                   s->echo = ssh2_pkt_getbool(pktin);
-                   s->need_pw = TRUE;
-               } else
-                   s->need_pw = FALSE;
-           }
-
-           if (!s->method && s->can_passwd) {
-               s->method = AUTH_PASSWORD;
-               ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-               ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
-               sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username,
-                       ssh->savedhost);
-               s->need_pw = TRUE;
-           }
 
-           if (s->need_pw) {
-               if (ssh_get_line) {
-                   if (!ssh_get_line(s->pwprompt, s->password,
-                                     sizeof(s->password), TRUE)) {
-                       /*
-                        * get_line failed to get a password (for
-                        * example because one was supplied on the
-                        * command line which has already failed to
-                        * work). Terminate.
-                        */
-                       ssh_disconnect(ssh, NULL, "Unable to authenticate",
-                                      SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
-                                      FALSE);
-                       crStopV;
+                   /*
+                    * Try decrypting the key.
+                    */
+                   key = ssh2_load_userkey(&ssh->cfg.keyfile, passphrase,
+                                           &error);
+                   if (passphrase) {
+                       /* burn the evidence */
+                       memset(passphrase, 0, strlen(passphrase));
+                       sfree(passphrase);
+                   }
+                   if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+                       if (passphrase &&
+                           (key == SSH2_WRONG_PASSPHRASE)) {
+                           c_write_str(ssh, "Wrong passphrase\r\n");
+                           key = NULL;
+                           /* and loop again */
+                       } else {
+                           c_write_str(ssh, "Unable to load private key (");
+                           c_write_str(ssh, error);
+                           c_write_str(ssh, ")\r\n");
+                           key = NULL;
+                           break; /* try something else */
+                       }
                    }
-               } else {
-                   int ret;           /* need not be saved across crReturn */
-                   c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt));
-                   ssh->send_ok = 1;
-
-                   setup_userpass_input(ssh, s->password,
-                                        sizeof(s->password), s->echo);
-                   do {
-                       crWaitUntilV(!pktin);
-                       ret = process_userpass_input(ssh, in, inlen);
-                   } while (ret == 0);
-                   if (ret < 0)
-                       cleanup_exit(0);
-                   c_write_str(ssh, "\r\n");
                }
-           }
 
-           if (s->method == AUTH_PUBLICKEY_FILE) {
-               /*
-                * We have our passphrase. Now try the actual authentication.
-                */
-               struct ssh2_userkey *key;
-               const char *error = NULL;
-
-               key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password,
-                                       &error);
-               if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
-                   if (key == SSH2_WRONG_PASSPHRASE) {
-                       c_write_str(ssh, "Wrong passphrase\r\n");
-                       s->tried_pubkey_config = FALSE;
-                   } else {
-                       c_write_str(ssh, "Unable to load private key (");
-                       c_write_str(ssh, error);
-                       c_write_str(ssh, ")\r\n");
-                       s->tried_pubkey_config = TRUE;
-                   }
-                   /* Send a spurious AUTH_NONE to return to the top. */
-                   s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
-                   ssh2_pkt_addstring(s->pktout, s->username);
-                   ssh2_pkt_addstring(s->pktout, "ssh-connection");    /* service requested */
-                   ssh2_pkt_addstring(s->pktout, "none");      /* method */
-                   ssh2_pkt_send(ssh, s->pktout);
-                   s->type = AUTH_TYPE_NONE;
-               } else {
+               if (key) {
                    unsigned char *pkblob, *sigblob, *sigdata;
                    int pkblob_len, sigblob_len, sigdata_len;
-                    int p;
+                   int p;
 
                    /*
                     * We have loaded the private key and the server
@@ -7196,13 +7105,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     */
                    s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                    ssh2_pkt_addstring(s->pktout, s->username);
-                   ssh2_pkt_addstring(s->pktout, "ssh-connection");    /* service requested */
-                   ssh2_pkt_addstring(s->pktout, "publickey"); /* method */
+                   ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                                   /* service requested */
+                   ssh2_pkt_addstring(s->pktout, "publickey");
+                                                   /* method */
                    ssh2_pkt_addbool(s->pktout, TRUE);
+                                                   /* signature follows */
                    ssh2_pkt_addstring(s->pktout, key->alg->name);
-                   pkblob = key->alg->public_blob(key->data, &pkblob_len);
+                   pkblob = key->alg->public_blob(key->data,
+                                                  &pkblob_len);
                    ssh2_pkt_addstring_start(s->pktout);
-                   ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, pkblob_len);
+                   ssh2_pkt_addstring_data(s->pktout, (char *)pkblob,
+                                           pkblob_len);
 
                    /*
                     * The data to be signed is:
@@ -7214,21 +7128,21 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     */
                    sigdata_len = s->pktout->length - 5 + 4 +
                        ssh->v2_session_id_len;
-                    if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                        sigdata_len -= 4;
+                   if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+                       sigdata_len -= 4;
                    sigdata = snewn(sigdata_len, unsigned char);
-                    p = 0;
-                    if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
-                        PUT_32BIT(sigdata+p, ssh->v2_session_id_len);
-                        p += 4;
-                    }
+                   p = 0;
+                   if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+                       PUT_32BIT(sigdata+p, ssh->v2_session_id_len);
+                       p += 4;
+                   }
                    memcpy(sigdata+p, ssh->v2_session_id,
                           ssh->v2_session_id_len);
                    p += ssh->v2_session_id_len;
                    memcpy(sigdata+p, s->pktout->data + 5,
                           s->pktout->length - 5);
-                    p += s->pktout->length - 5;
-                    assert(p == sigdata_len);
+                   p += s->pktout->length - 5;
+                   assert(p == sigdata_len);
                    sigblob = key->alg->sign(key->data, (char *)sigdata,
                                             sigdata_len, &sigblob_len);
                    ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len,
@@ -7241,8 +7155,169 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    s->type = AUTH_TYPE_PUBLICKEY;
                    key->alg->freekey(key->data);
                }
-           } else if (s->method == AUTH_PASSWORD) {
+
+           } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+               /*
+                * Keyboard-interactive authentication.
+                */
+               char *name, *inst, *lang;
+               int name_len, inst_len, lang_len;
+               int i;
+
+               s->method = AUTH_KEYBOARD_INTERACTIVE;
+               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+               ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+               s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+               ssh2_pkt_addstring(s->pktout, s->username);
+               ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                                       /* service requested */
+               ssh2_pkt_addstring(s->pktout, "keyboard-interactive");
+                                                       /* method */
+               ssh2_pkt_addstring(s->pktout, "");      /* lang */
+               ssh2_pkt_addstring(s->pktout, "");      /* submethods */
+               ssh2_pkt_send(ssh, s->pktout);
+
+               crWaitUntilV(pktin);
+               if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                   /* Server is not willing to do keyboard-interactive
+                    * at all. Give up on it entirely. */
+                   s->gotit = TRUE;
+                   if (pktin->type == SSH2_MSG_USERAUTH_FAILURE)
+                       logevent("Keyboard-interactive authentication refused");
+                   s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+                   s->kbd_inter_refused = TRUE; /* don't try it again */
+                   continue;
+               }
+
+               /*
+                * We've got a fresh USERAUTH_INFO_REQUEST.
+                * Get the preamble and start building a prompt.
+                */
+               ssh_pkt_getstring(pktin, &name, &name_len);
+               ssh_pkt_getstring(pktin, &inst, &inst_len);
+               ssh_pkt_getstring(pktin, &lang, &lang_len);
+               s->cur_prompt = new_prompts(ssh->frontend);
+               s->cur_prompt->to_server = TRUE;
+               if (name_len) {
+                   /* FIXME: better prefix to distinguish from
+                    * local prompts? */
+                   s->cur_prompt->name = dupprintf("SSH server: %.*s",
+                                                   name_len, name);
+                   s->cur_prompt->name_reqd = TRUE;
+               } else {
+                   s->cur_prompt->name = dupstr("SSH server authentication");
+                   s->cur_prompt->name_reqd = FALSE;
+               }
+               s->cur_prompt->instruction =
+                   dupprintf("Using keyboard-interactive authentication.%s%.*s",
+                             inst_len ? "\n" : "", inst_len, inst);
+               s->cur_prompt->instr_reqd = TRUE;
+
                /*
+                * Get the prompts from the packet.
+                */
+               s->num_prompts = ssh_pkt_getuint32(pktin);
+               for (i = 0; i < s->num_prompts; i++) {
+                   char *prompt;
+                   int prompt_len;
+                   int echo;
+                   static char noprompt[] =
+                       "<server failed to send prompt>: ";
+
+                   ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+                   echo = ssh2_pkt_getbool(pktin);
+                   if (!prompt_len) {
+                       prompt = noprompt;
+                       prompt_len = lenof(noprompt)-1;
+                   }
+                   add_prompt(s->cur_prompt,
+                              dupprintf("%.*s", prompt_len, prompt),
+                              echo, SSH_MAX_PASSWORD_LEN);
+               }
+
+               /*
+                * Get the user's responses.
+                */
+               if (s->num_prompts) {
+                   int ret; /* not live over crReturn */
+                   ret = get_userpass_input(s->cur_prompt, NULL, 0);
+                   while (ret < 0) {
+                       ssh->send_ok = 1;
+                       crWaitUntilV(!pktin);
+                       ret = get_userpass_input(s->cur_prompt, in, inlen);
+                       ssh->send_ok = 0;
+                   }
+                   if (!ret) {
+                       /*
+                        * Failed to get responses. Terminate.
+                        */
+                       free_prompts(s->cur_prompt);
+                       ssh_disconnect(ssh, NULL, "Unable to authenticate",
+                                      SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+                                      TRUE);
+                       crStopV;
+                   }
+               }
+
+               /*
+                * Send the responses to the server.
+                */
+               s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+               s->pktout->forcepad = 256;
+               ssh2_pkt_adduint32(s->pktout, s->num_prompts);
+               for (i=0; i < s->num_prompts; i++) {
+                   dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
+                   ssh2_pkt_addstring(s->pktout,
+                                      s->cur_prompt->prompts[i]->result);
+                   end_log_omission(ssh, s->pktout);
+               }
+               ssh2_pkt_send(ssh, s->pktout);
+               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/
+
+           } else if (s->can_passwd) {
+
+               /*
+                * Plain old password authentication.
+                */
+               int ret; /* not live over crReturn */
+
+               s->method = AUTH_PASSWORD;
+               ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
+
+               s->cur_prompt = new_prompts(ssh->frontend);
+               s->cur_prompt->to_server = TRUE;
+               s->cur_prompt->name = dupstr("SSH password");
+               add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ",
+                                                   s->username,
+                                                   ssh->savedhost),
+                          FALSE, SSH_MAX_PASSWORD_LEN);
+
+               ret = get_userpass_input(s->cur_prompt, NULL, 0);
+               while (ret < 0) {
+                   ssh->send_ok = 1;
+                   crWaitUntilV(!pktin);
+                   ret = get_userpass_input(s->cur_prompt, in, inlen);
+                   ssh->send_ok = 0;
+               }
+               if (!ret) {
+                   /*
+                    * Failed to get responses. Terminate.
+                    */
+                   free_prompts(s->cur_prompt);
+                   ssh_disconnect(ssh, NULL, "Unable to authenticate",
+                                  SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+                                  TRUE);
+                   crStopV;
+               }
+
+               /*
+                * Send the password packet.
+                *
                 * We pad out the password packet to 256 bytes to make
                 * it harder for an attacker to find the length of the
                 * user's password.
@@ -7254,54 +7329,39 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                s->pktout->forcepad = 256;
                ssh2_pkt_addstring(s->pktout, s->username);
-               ssh2_pkt_addstring(s->pktout, "ssh-connection");        /* service requested */
+               ssh2_pkt_addstring(s->pktout, "ssh-connection");
+                                                       /* service requested */
                ssh2_pkt_addstring(s->pktout, "password");
                ssh2_pkt_addbool(s->pktout, FALSE);
                dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
-               ssh2_pkt_addstring(s->pktout, s->password);
-               memset(s->password, 0, sizeof(s->password));
+               ssh2_pkt_addstring(s->pktout,
+                                  s->cur_prompt->prompts[0]->result);
+               free_prompts(s->cur_prompt);
                end_log_omission(ssh, s->pktout);
                ssh2_pkt_send(ssh, s->pktout);
                logevent("Sent password");
-               s->type = AUTH_TYPE_PASSWORD;
-           } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) {
-               if (s->curr_prompt == 0) {
-                   s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
-                   s->pktout->forcepad = 256;
-                   ssh2_pkt_adduint32(s->pktout, s->num_prompts);
-               }
-               if (s->need_pw) {      /* only add pw if we just got one! */
-                   dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
-                   ssh2_pkt_addstring(s->pktout, s->password);
-                   memset(s->password, 0, sizeof(s->password));
-                   end_log_omission(ssh, s->pktout);
-                   s->curr_prompt++;
-               }
-               if (s->curr_prompt >= s->num_prompts) {
-                   ssh2_pkt_send(ssh, s->pktout);
-               } else {
-                   /*
-                    * If there are prompts remaining, we set
-                    * `gotit' so that we won't attempt to get
-                    * another packet. Then we go back round the
-                    * loop and will end up retrieving another
-                    * prompt out of the existing packet. Funky or
-                    * what?
-                    */
-                   s->gotit = TRUE;
-               }
-               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+               s->type = AUTH_TYPE_PASSWORD; /*FIXME?*/
+
            } else {
+
                ssh_disconnect(ssh, NULL,
                               "No supported authentication methods available",
                               SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
                               FALSE);
                crStopV;
+
            }
+
        }
     }
     ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
 
+    /* Clear up various bits and pieces from authentication. */
+    if (s->publickey_blob) {
+       sfree(s->publickey_blob);
+       sfree(s->publickey_comment);
+    }
+
     /*
      * Now the connection protocol has started, one way or another.
      */
index ad9acc7..d712fc9 100644 (file)
@@ -6257,6 +6257,24 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len)
     return 0;
 }
 
+/*
+ * Write untrusted data to the terminal.
+ * The only control character that should be honoured is \n (which
+ * will behave as a CRLF).
+ */
+int term_data_untrusted(Terminal *term, const char *data, int len)
+{
+    int i;
+    /* FIXME: more sophisticated checking? */
+    for (i = 0; i < len; i++) {
+       if (data[i] == '\n')
+           term_data(term, 1, "\r\n", 2);
+       else if (data[i] & 0x60)
+           term_data(term, 1, data + i, 1);
+    }
+    return 0; /* assumes that term_data() always returns 0 */
+}
+
 void term_provide_logctx(Terminal *term, void *logctx)
 {
     term->logctx = logctx;
@@ -6281,3 +6299,128 @@ char *term_get_ttymode(Terminal *term, const char *mode)
     /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */
     return dupstr(val);
 }
+
+struct term_userpass_state {
+    size_t curr_prompt;
+    int done_prompt;   /* printed out prompt yet? */
+    size_t pos;                /* cursor position */
+};
+
+/*
+ * Process some terminal data in the course of username/password
+ * input.
+ */
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+                           unsigned char *in, int inlen)
+{
+    struct term_userpass_state *s = (struct term_userpass_state *)p->data;
+    if (!s) {
+       /*
+        * First call. Set some stuff up.
+        */
+       p->data = s = snew(struct term_userpass_state);
+       s->curr_prompt = 0;
+       s->done_prompt = 0;
+       /* We only print the `name' caption if we have to... */
+       if (p->name_reqd && p->name) {
+           size_t l = strlen(p->name);
+           term_data_untrusted(term, p->name, l);
+           if (p->name[l-1] != '\n')
+               term_data_untrusted(term, "\n", 1);
+       }
+       /* ...but we always print any `instruction'. */
+       if (p->instruction) {
+           size_t l = strlen(p->instruction);
+           term_data_untrusted(term, p->instruction, l);
+           if (p->instruction[l-1] != '\n')
+               term_data_untrusted(term, "\n", 1);
+       }
+       /*
+        * Zero all the results, in case we abort half-way through.
+        */
+       {
+           int i;
+           for (i = 0; i < p->n_prompts; i++)
+               memset(p->prompts[i]->result, 0, p->prompts[i]->result_len);
+       }
+    }
+
+    while (s->curr_prompt < p->n_prompts) {
+
+       prompt_t *pr = p->prompts[s->curr_prompt];
+       int finished_prompt = 0;
+
+       if (!s->done_prompt) {
+           term_data_untrusted(term, pr->prompt, strlen(pr->prompt));
+           s->done_prompt = 1;
+           s->pos = 0;
+       }
+
+       /* Breaking out here ensures that the prompt is printed even
+        * if we're now waiting for user data. */
+       if (!in || !inlen) break;
+
+       /* FIXME: should we be using local-line-editing code instead? */
+       while (!finished_prompt && inlen) {
+           char c = *in++;
+           inlen--;
+           switch (c) {
+             case 10:
+             case 13:
+               term_data(term, 0, "\r\n", 2);
+               pr->result[s->pos] = '\0';
+               pr->result[pr->result_len - 1] = '\0';
+               /* go to next prompt, if any */
+               s->curr_prompt++;
+               s->done_prompt = 0;
+               finished_prompt = 1; /* break out */
+               break;
+             case 8:
+             case 127:
+               if (s->pos > 0) {
+                   if (pr->echo)
+                       term_data(term, 0, "\b \b", 3);
+                   s->pos--;
+               }
+               break;
+             case 21:
+             case 27:
+               while (s->pos > 0) {
+                   if (pr->echo)
+                       term_data(term, 0, "\b \b", 3);
+                   s->pos--;
+               }
+               break;
+             case 3:
+             case 4:
+               /* Immediate abort. */
+               term_data(term, 0, "\r\n", 2);
+               sfree(s);
+               return 0; /* user abort */
+             default:
+               /*
+                * This simplistic check for printability is disabled
+                * when we're doing password input, because some people
+                * have control characters in their passwords.
+                */
+               if ((!pr->echo ||
+                    (c >= ' ' && c <= '~') ||
+                    ((unsigned char) c >= 160))
+                   && s->pos < pr->result_len - 1) {
+                   pr->result[s->pos++] = c;
+                   if (pr->echo)
+                       term_data(term, 0, &c, 1);
+               }
+               break;
+           }
+       }
+       
+    }
+
+    if (s->curr_prompt < p->n_prompts) {
+       return -1; /* more data required */
+    } else {
+       sfree(s);
+       return +1; /* all done */
+    }
+}
index 6818fff..6193a04 100644 (file)
@@ -189,6 +189,22 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len)
     return term_data(inst->term, is_stderr, data, len);
 }
 
+int from_backend_untrusted(void *frontend, const char *data, int len)
+{
+    struct gui_data *inst = (struct gui_data *)frontend;
+    return term_data_untrusted(inst->term, data, len);
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    struct gui_data *inst = (struct gui_data *)p->frontend;
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = term_get_userpass_input(inst->term, p, in, inlen);
+    return ret;
+}
+
 void logevent(void *frontend, const char *string)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
index 7652e23..d4848a6 100644 (file)
@@ -280,41 +280,81 @@ void logevent(void *frontend, const char *string)
        log_eventlog(console_logctx, string);
 }
 
-int console_get_line(const char *prompt, char *str,
-                    int maxlen, int is_pw)
+static void console_data_untrusted(const char *data, int len)
 {
-    struct termios oldmode, newmode;
     int i;
+    for (i = 0; i < len; i++)
+       if ((data[i] & 0x60) || (data[i] == '\n'))
+           fputc(data[i], stdout);
+    fflush(stdout);
+}
 
-    if (console_batch_mode) {
-       if (maxlen > 0)
-           str[0] = '\0';
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    size_t curr_prompt;
+
+    /*
+     * Zero all the results, in case we abort half-way through.
+     */
+    {
+       int i;
+       for (i = 0; i < p->n_prompts; i++)
+           memset(p->prompts[i]->result, 0, p->prompts[i]->result_len);
+    }
+
+    if (console_batch_mode)
        return 0;
-    } else {
+
+    /*
+     * Preamble.
+     */
+    /* We only print the `name' caption if we have to... */
+    if (p->name_reqd && p->name) {
+       size_t l = strlen(p->name);
+       console_data_untrusted(p->name, l);
+       if (p->name[l-1] != '\n')
+           console_data_untrusted("\n", 1);
+    }
+    /* ...but we always print any `instruction'. */
+    if (p->instruction) {
+       size_t l = strlen(p->instruction);
+       console_data_untrusted(p->instruction, l);
+       if (p->instruction[l-1] != '\n')
+           console_data_untrusted("\n", 1);
+    }
+
+    for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+       struct termios oldmode, newmode;
+       int i;
+       prompt_t *pr = p->prompts[curr_prompt];
+
        tcgetattr(0, &oldmode);
        newmode = oldmode;
        newmode.c_lflag |= ISIG | ICANON;
-       if (is_pw)
+       if (!pr->echo)
            newmode.c_lflag &= ~ECHO;
        else
            newmode.c_lflag |= ECHO;
        tcsetattr(0, TCSANOW, &newmode);
 
-       fputs(prompt, stdout);
-       fflush(stdout);
-       i = read(0, str, maxlen - 1);
+       console_data_untrusted(pr->prompt, strlen(pr->prompt));
+
+       i = read(0, pr->result, pr->result_len - 1);
 
        tcsetattr(0, TCSANOW, &oldmode);
 
-       if (i > 0 && str[i-1] == '\n')
+       if (i > 0 && pr->result[i-1] == '\n')
            i--;
-       str[i] = '\0';
+       pr->result[i] = '\0';
 
-       if (is_pw)
+       if (!pr->echo)
            fputs("\n", stdout);
 
-       return 1;
     }
+
+    return 1; /* success */
+
 }
 
 void frontend_keypress(void *handle)
index 13dea44..90b151f 100644 (file)
@@ -394,6 +394,25 @@ int from_backend(void *frontend_handle, int is_stderr,
     return osize + esize;
 }
 
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+    /*
+     * No "untrusted" output should get here (the way the code is
+     * currently, it's all diverted by FLAG_STDERR).
+     */
+    assert(!"Unexpected call to from_backend_untrusted()");
+    return 0; /* not reached */
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = console_get_userpass_input(p, in, inlen);
+    return ret;
+}
+
 /*
  * Handle data from a local tty in PARMRK format.
  */
@@ -534,8 +553,6 @@ int main(int argc, char **argv)
     void *ldisc, *logctx;
     long now;
 
-    ssh_get_line = console_get_line;
-
     fdlist = NULL;
     fdcount = fdsize = 0;
     /*
index 3691cac..0779ff1 100644 (file)
@@ -70,6 +70,15 @@ Filename platform_default_filename(const char *name)
 
 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
 
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = console_get_userpass_input(p, in, inlen);
+    return ret;
+}
+
 /*
  * Stubs for the GUI feedback mechanism in Windows PSCP.
  */
index c38fa01..e0f0215 100644 (file)
@@ -298,48 +298,90 @@ void logevent(void *frontend, const char *string)
        log_eventlog(console_logctx, string);
 }
 
-int console_get_line(const char *prompt, char *str,
-                           int maxlen, int is_pw)
+static void console_data_untrusted(HANDLE hout, const char *data, int len)
+{
+    DWORD dummy;
+    /* FIXME: control-character filtering */
+    WriteFile(hout, data, len, &dummy, NULL);
+}
+
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
     HANDLE hin, hout;
-    DWORD savemode, newmode, i;
+    size_t curr_prompt;
 
-    if (console_batch_mode) {
-       if (maxlen > 0)
-           str[0] = '\0';
+    /*
+     * Zero all the results, in case we abort half-way through.
+     */
+    {
+       int i;
+       for (i = 0; i < p->n_prompts; i++)
+           memset(p->prompts[i]->result, 0, p->prompts[i]->result_len);
+    }
+
+    if (console_batch_mode)
        return 0;
-    } else {
-       hin = GetStdHandle(STD_INPUT_HANDLE);
-       hout = GetStdHandle(STD_OUTPUT_HANDLE);
-       if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
-           fprintf(stderr, "Cannot get standard input/output handles\n");
-           cleanup_exit(1);
-       }
+
+    hin = GetStdHandle(STD_INPUT_HANDLE);
+    hout = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
+       fprintf(stderr, "Cannot get standard input/output handles\n");
+       cleanup_exit(1);
+    }
+
+    /*
+     * Preamble.
+     */
+    /* We only print the `name' caption if we have to... */
+    if (p->name_reqd && p->name) {
+       size_t l = strlen(p->name);
+       console_data_untrusted(hout, p->name, l);
+       if (p->name[l-1] != '\n')
+           console_data_untrusted(hout, "\n", 1);
+    }
+    /* ...but we always print any `instruction'. */
+    if (p->instruction) {
+       size_t l = strlen(p->instruction);
+       console_data_untrusted(hout, p->instruction, l);
+       if (p->instruction[l-1] != '\n')
+           console_data_untrusted(hout, "\n", 1);
+    }
+
+    for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+       DWORD savemode, newmode, i = 0;
+       prompt_t *pr = p->prompts[curr_prompt];
+       BOOL r;
 
        GetConsoleMode(hin, &savemode);
        newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
-       if (is_pw)
+       if (!pr->echo)
            newmode &= ~ENABLE_ECHO_INPUT;
        else
            newmode |= ENABLE_ECHO_INPUT;
        SetConsoleMode(hin, newmode);
 
-       WriteFile(hout, prompt, strlen(prompt), &i, NULL);
-       ReadFile(hin, str, maxlen - 1, &i, NULL);
+       console_data_untrusted(hout, pr->prompt, strlen(pr->prompt));
+
+       r = ReadFile(hin, pr->result, pr->result_len - 1, &i, NULL);
 
        SetConsoleMode(hin, savemode);
 
-       if ((int) i > maxlen)
-           i = maxlen - 1;
+       if ((int) i > pr->result_len)
+           i = pr->result_len - 1;
        else
            i = i - 2;
-       str[i] = '\0';
+       pr->result[i] = '\0';
 
-       if (is_pw)
-           WriteFile(hout, "\r\n", 2, &i, NULL);
+       if (!pr->echo) {
+           DWORD dummy;
+           WriteFile(hout, "\r\n", 2, &dummy, NULL);
+       }
 
-       return 1;
     }
+
+    return 1; /* success */
+
 }
 
 void frontend_keypress(void *handle)
index 0bd800f..43a5e24 100644 (file)
@@ -5069,6 +5069,20 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len)
     return term_data(term, is_stderr, data, len);
 }
 
+int from_backend_untrusted(void *frontend, const char *data, int len)
+{
+    return term_data_untrusted(term, data, len);
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = term_get_userpass_input(term, p, in, inlen);
+    return ret;
+}
+
 void agent_schedule_callback(void (*callback)(void *, void *, int),
                             void *callback_ctx, void *data, int len)
 {
index a67a237..f6596ee 100644 (file)
@@ -189,6 +189,25 @@ int from_backend(void *frontend_handle, int is_stderr,
     return osize + esize;
 }
 
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+    /*
+     * No "untrusted" output should get here (the way the code is
+     * currently, it's all diverted by FLAG_STDERR).
+     */
+    assert(!"Unexpected call to from_backend_untrusted()");
+    return 0; /* not reached */
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = console_get_userpass_input(p, in, inlen);
+    return ret;
+}
+
 static DWORD main_thread_id;
 
 void agent_schedule_callback(void (*callback)(void *, void *, int),
@@ -285,8 +304,6 @@ int main(int argc, char **argv)
     int use_subsystem = 0;
     long now, next;
 
-    ssh_get_line = console_get_line;
-
     sklist = NULL;
     skcount = sksize = 0;
     /*
index 6eecb31..47d3c43 100644 (file)
@@ -127,6 +127,15 @@ void gui_enable(char *arg)
 
 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
 
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+    int ret;
+    ret = cmdline_get_passwd_input(p, in, inlen);
+    if (ret == -1)
+       ret = console_get_userpass_input(p, in, inlen);
+    return ret;
+}
+
 /* ----------------------------------------------------------------------
  * File access abstraction.
  */