Add BUG_SSH2_DERIVEKEY, present (according to OpenSSH) in ssh.com
[sgt/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 6a653e5..19c93df 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -181,6 +181,8 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_HMAC                             2
 #define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
 #define BUG_CHOKES_ON_RSA                        8
+#define BUG_SSH2_RSA_PADDING                    16
+#define BUG_SSH2_DERIVEKEY                       32
 
 static int ssh_pkt_ctx = 0;
 
@@ -514,8 +516,6 @@ static const struct ssh_compress *sccomp = NULL;
 static const struct ssh_kex *kex = NULL;
 static const struct ssh_signkey *hostkey = NULL;
 static unsigned char ssh2_session_id[20];
-int (*ssh_get_line) (const char *prompt, char *str, int maxlen,
-                    int is_pw) = NULL;
 
 static char *savedhost;
 static int savedport;
@@ -886,6 +886,15 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
     st->pad = pktin.data[4];
 
     /*
+     * _Completely_ silly lengths should be stomped on before they
+     * do us any more damage.
+     */
+    if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
+       bombout(("Incoming packet was garbled on decryption"));
+       crReturn(0);
+    }
+
+    /*
      * This enables us to deduce the payload length.
      */
     st->payload = st->len - st->pad - 1;
@@ -1540,6 +1549,7 @@ static int ssh2_pkt_getbool(void)
 static void ssh2_pkt_getstring(char **p, int *length)
 {
     *p = NULL;
+    *length = 0;
     if (pktin.length - pktin.savedpos < 4)
        return;
     *length = GET_32BIT(pktin.data + pktin.savedpos);
@@ -1567,6 +1577,75 @@ static Bignum ssh2_pkt_getmp(void)
 }
 
 /*
+ * Helper function to add an SSH2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(void *pkblob_v, int pkblob_len,
+                            void *sigblob_v, int sigblob_len)
+{
+    unsigned char *pkblob = (unsigned char *)pkblob_v;
+    unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+    /* dmemdump(pkblob, pkblob_len); */
+    /* dmemdump(sigblob, sigblob_len); */
+
+    /*
+     * See if this is in fact an ssh-rsa signature and a buggy
+     * server; otherwise we can just do this the easy way.
+     */
+    if ((ssh_remote_bugs & BUG_SSH2_RSA_PADDING) &&
+       (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
+       int pos, len, siglen;
+
+       /*
+        * Find the byte length of the modulus.
+        */
+
+       pos = 4+7;                     /* skip over "ssh-rsa" */
+       pos += 4 + GET_32BIT(pkblob+pos);   /* skip over exponent */
+       len = GET_32BIT(pkblob+pos);   /* find length of modulus */
+       pos += 4;                      /* find modulus itself */
+       while (len > 0 && pkblob[pos] == 0)
+           len--, pos++;
+       /* debug(("modulus length is %d\n", len)); */
+
+       /*
+        * Now find the signature integer.
+        */
+       pos = 4+7;                     /* skip over "ssh-rsa" */
+       siglen = GET_32BIT(sigblob+pos);
+       /* debug(("signature length is %d\n", siglen)); */
+
+       if (len != siglen) {
+           unsigned char newlen[4];
+           ssh2_pkt_addstring_start();
+           ssh2_pkt_addstring_data(sigblob, pos);
+           /* dmemdump(sigblob, pos); */
+           pos += 4;                  /* point to start of actual sig */
+           PUT_32BIT(newlen, len);
+           ssh2_pkt_addstring_data(newlen, 4);
+           /* dmemdump(newlen, 4); */
+           newlen[0] = 0;
+           while (len-- > siglen) {
+               ssh2_pkt_addstring_data(newlen, 1);
+               /* dmemdump(newlen, 1); */
+           }
+           ssh2_pkt_addstring_data(sigblob+pos, siglen);
+           /* dmemdump(sigblob+pos, siglen); */
+           return;
+       }
+
+       /* Otherwise fall through and do it the easy way. */
+    }
+
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_data(sigblob, sigblob_len);
+}
+
+/*
  * Examine the remote side's version string and compare it against
  * a list of known buggy implementations.
  */
@@ -1622,6 +1701,25 @@ static void ssh_detect_bugs(char *vstring)
        ssh_remote_bugs |= BUG_SSH2_HMAC;
        logevent("We believe remote version has SSH2 HMAC bug");
     }
+
+    if (!strncmp(imp, "2.0.", 4)) {
+       /*
+        * These versions have the key-derivation bug (failing to
+        * include the literal shared secret in the hashes that
+        * generate the keys).
+        */
+       ssh_remote_bugs |= BUG_SSH2_DERIVEKEY;
+       logevent("We believe remote version has SSH2 key-derivation bug");
+    }
+
+    if ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') ||
+       (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')) {
+       /*
+        * These versions have the SSH2 RSA padding bug.
+        */
+       ssh_remote_bugs |= BUG_SSH2_RSA_PADDING;
+       logevent("We believe remote version has SSH2 RSA padding bug");
+    }
 }
 
 static int do_ssh_init(unsigned char c)
@@ -1632,6 +1730,7 @@ static int do_ssh_init(unsigned char c)
     static int vstrsize;
     static char *vlog;
     static int i;
+    static int proto1, proto2;
 
     crBegin;
 
@@ -1688,12 +1787,26 @@ static int do_ssh_init(unsigned char c)
     sfree(vlog);
 
     /*
-     * Server version "1.99" means we can choose whether we use v1
-     * or v2 protocol. Choice is based on cfg.sshprot.
+     * Decide which SSH protocol version to support.
      */
-    if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
+
+    /* Anything strictly below "2.0" means protocol 1 is supported. */
+    proto1 = ssh_versioncmp(version, "2.0") < 0;
+    /* Anything greater or equal to "1.99" means protocol 2 is supported. */
+    proto2 = ssh_versioncmp(version, "1.99") >= 0;
+
+    if (cfg.sshprot == 0 && !proto1) {
+       bombout(("SSH protocol version 1 required by user but not provided by server"));
+       crReturn(0);
+    }
+    if (cfg.sshprot == 3 && !proto2) {
+       bombout(("SSH protocol version 2 required by user but not provided by server"));
+       crReturn(0);
+    }
+
+    if (proto2 && (cfg.sshprot >= 2 || !proto1)) {
        /*
-        * This is a v2 server. Begin v2 protocol.
+        * Use v2 protocol.
         */
        char verstring[80], vlog[100];
        sprintf(verstring, "SSH-2.0-%s", sshver);
@@ -1713,7 +1826,7 @@ static int do_ssh_init(unsigned char c)
        s_rdpkt = ssh2_rdpkt;
     } else {
        /*
-        * This is a v1 server. Begin v1 protocol.
+        * Use v1 protocol.
         */
        char verstring[80], vlog[100];
        sprintf(verstring, "SSH-%s-%s",
@@ -1722,6 +1835,7 @@ static int do_ssh_init(unsigned char c)
        sprintf(vlog, "We claim version: %s", verstring);
        logevent(vlog);
        strcat(verstring, "\n");
+
        logevent("Using SSH protocol version 1");
        sk_write(s, verstring, strlen(verstring));
        ssh_protocol = ssh1_protocol;
@@ -1955,6 +2069,76 @@ static void ssh_throttle_all(int enable, int bufsize)
 }
 
 /*
+ * Username and password input, abstracted off into reusable
+ * routines (hopefully even reusable between SSH1 and SSH2!).
+ */
+static char *ssh_userpass_input_buffer;
+static int ssh_userpass_input_buflen;
+static int ssh_userpass_input_bufpos;
+static int ssh_userpass_input_echo;
+
+/* Set up a username or password input loop on a given buffer. */
+void setup_userpass_input(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).
+ */
+int process_userpass_input(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("\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("\b \b");
+               ssh_userpass_input_bufpos--;
+           }
+           break;
+         case 3:
+         case 4:
+           return -1;
+           break;
+         default:
+           if (((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(&c, 1);
+           }
+           break;
+       }
+    }
+    return 0;
+}
+
+/*
  * Handle the key exchange and user authentication phases.
  */
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
@@ -2139,10 +2323,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     fflush(stdout);
     {
-       static int pos = 0;
-       static char c;
        if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           if (ssh_get_line) {
+           if (ssh_get_line && !ssh_getline_pw_only) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
                    /*
@@ -2154,47 +2336,18 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                    crReturn(1);
                }
            } else {
+               static int ret;
                c_write_str("login as: ");
                ssh_send_ok = 1;
-               while (pos >= 0) {
+
+               setup_userpass_input(username, sizeof(username), 1);
+               do {
                    crWaitUntil(!ispkt);
-                   while (inlen--)
-                       switch (c = *in++) {
-                         case 10:
-                         case 13:
-                           username[pos] = 0;
-                           pos = -1;
-                           break;
-                         case 8:
-                         case 127:
-                           if (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 21:
-                         case 27:
-                           while (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 3:
-                         case 4:
-                           cleanup_exit(0);
-                           break;
-                         default:
-                           if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160))
-                               && pos < sizeof(username)-1) {
-                               username[pos++] = c;
-                               c_write(&c, 1);
-                           }
-                           break;
-                       }
-               }
+                   ret = process_userpass_input(in, inlen);
+               } while (ret == 0);
+               if (ret < 0)
+                   cleanup_exit(0);
                c_write_str("\r\n");
-               username[strcspn(username, "\n\r")] = '\0';
            }
        } else {
            strncpy(username, cfg.username, 99);
@@ -2430,8 +2583,22 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        }
        if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
            char *comment = NULL;
+           int type;
+           char msgbuf[256];
            if (flags & FLAG_VERBOSE)
                c_write_str("Trying public key authentication.\r\n");
+           sprintf(msgbuf, "Trying public key \"%.200s\"", cfg.keyfile);
+           logevent(msgbuf);
+           type = key_type(cfg.keyfile);
+           if (type != SSH_KEYTYPE_SSH1) {
+               sprintf(msgbuf, "Key is of wrong type (%s)",
+                       key_type_to_str(type));
+               logevent(msgbuf);
+               c_write_str(msgbuf);
+               c_write_str("\r\n");
+               tried_publickey = 1;
+               continue;
+           }
            if (!rsakey_encrypted(cfg.keyfile, &comment)) {
                if (flags & FLAG_VERBOSE)
                    c_write_str("No passphrase required.\r\n");
@@ -2464,37 +2631,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        } else {
            /* Prompt may have come from server. We've munged it a bit, so
             * we know it to be zero-terminated at least once. */
+           static int ret;
            c_write_untrusted(prompt, strlen(prompt));
            pos = 0;
-           ssh_send_ok = 1;
-           while (pos >= 0) {
+
+           setup_userpass_input(password, sizeof(password), 0);
+           do {
                crWaitUntil(!ispkt);
-               while (inlen--)
-                   switch (c = *in++) {
-                     case 10:
-                     case 13:
-                       password[pos] = 0;
-                       pos = -1;
-                       break;
-                     case 8:
-                     case 127:
-                       if (pos > 0)
-                           pos--;
-                       break;
-                     case 21:
-                     case 27:
-                       pos = 0;
-                       break;
-                     case 3:
-                     case 4:
-                       cleanup_exit(0);
-                       break;
-                     default:
-                       if (pos < sizeof(password)-1)
-                           password[pos++] = c;
-                       break;
-                   }
-           }
+               ret = process_userpass_input(in, inlen);
+           } while (ret == 0);
+           if (ret < 0)
+               cleanup_exit(0);
            c_write_str("\r\n");
        }
 
@@ -2719,7 +2866,7 @@ void sshfwd_close(struct ssh_channel *c)
         * on it now, and then when the server acks the channel
         * open, we can close it then.
         */
-       if (c->remoteid != -1) {
+       if (((int)c->remoteid) != -1) {
            if (ssh_version == 1) {
                send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                            PKT_END);
@@ -2831,7 +2978,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
     }
 
     {
-       char type, *e;
+       char type;
+       static char *e;
        int n;
        int sport,dport,sserv,dserv;
        char sports[256], dports[256], host[256];
@@ -2924,6 +3072,18 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                                    PKT_STR, host,
                                    PKT_INT, dport,
                                    PKT_END);
+                       do {
+                           crReturnV;
+                       } while (!ispkt);
+                       if (pktin.type != SSH1_SMSG_SUCCESS
+                           && pktin.type != SSH1_SMSG_FAILURE) {
+                           bombout(("Protocol confusion"));
+                           crReturnV;
+                       } else if (pktin.type == SSH1_SMSG_FAILURE) {
+                           c_write_str("Server refused port forwarding\r\n");
+                           ssh_editing = ssh_echoing = 1;
+                       }
+                       logevent("Remote port forwarding enabled");
                    }
                }
            }
@@ -3348,14 +3508,16 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr,
     SHA_State s;
     /* First 20 bytes. */
     SHA_Init(&s);
-    sha_mpint(&s, K);
+    if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+       sha_mpint(&s, K);
     SHA_Bytes(&s, H, 20);
     SHA_Bytes(&s, &chr, 1);
     SHA_Bytes(&s, sessid, 20);
     SHA_Final(&s, keyspace);
     /* Next 20 bytes. */
     SHA_Init(&s);
-    sha_mpint(&s, K);
+    if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+       sha_mpint(&s, K);
     SHA_Bytes(&s, H, 20);
     SHA_Bytes(&s, keyspace, 20);
     SHA_Final(&s, keyspace + 20);
@@ -3922,7 +4084,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     static int tried_pubkey_config, tried_agent, tried_keyb_inter;
     static int kbd_inter_running;
     static int we_are_in;
-    static int num_prompts, echo;
+    static int num_prompts, curr_prompt, echo;
     static char username[100];
     static int got_username;
     static char pwprompt[200];
@@ -3971,13 +4133,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     username[0] = '\0';
     got_username = FALSE;
     do {
-       static int pos;
-       static char c;
-
        /*
         * Get a username.
         */
-       pos = 0;
        if (got_username && !cfg.change_username) {
            /*
             * We got a username last time round this loop, and
@@ -3985,7 +4143,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
             * it again.
             */
        } else if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           if (ssh_get_line) {
+           if (ssh_get_line && !ssh_getline_pw_only) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
                    /*
@@ -3997,45 +4155,16 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    crReturnV;
                }
            } else {
+               static int ret;
                c_write_str("login as: ");
                ssh_send_ok = 1;
-               while (pos >= 0) {
+               setup_userpass_input(username, sizeof(username), 1);
+               do {
                    crWaitUntilV(!ispkt);
-                   while (inlen--)
-                       switch (c = *in++) {
-                         case 10:
-                         case 13:
-                           username[pos] = 0;
-                           pos = -1;
-                           break;
-                         case 8:
-                         case 127:
-                           if (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 21:
-                         case 27:
-                           while (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 3:
-                         case 4:
-                           cleanup_exit(0);
-                           break;
-                         default:
-                           if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160))
-                               && pos < sizeof(username)-1) {
-                               username[pos++] = c;
-                               c_write(&c, 1);
-                           }
-                           break;
-                       }
-               }
+                   ret = process_userpass_input(in, inlen);
+               } while (ret == 0);
+               if (ret < 0)
+                   cleanup_exit(0);
            }
            c_write_str("\r\n");
            username[strcspn(username, "\n\r")] = '\0';
@@ -4072,8 +4201,21 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        kbd_inter_running = FALSE;
        /* Load the pub half of cfg.keyfile so we notice if it's in Pageant */
        if (*cfg.keyfile) {
-           publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL,
-                                                 &publickey_bloblen);
+           int keytype;
+           logeventf("Reading private key file \"%.150s\"", cfg.keyfile);
+           keytype = key_type(cfg.keyfile);
+           if (keytype == SSH_KEYTYPE_SSH2)
+               publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL,
+                                                     &publickey_bloblen);
+           else {
+               char msgbuf[256];
+               logeventf("Unable to use this key file (%s)",
+                       key_type_to_str(keytype));
+               sprintf(msgbuf, "Unable to use key file \"%.150s\" (%s)\r\n",
+                       cfg.keyfile, key_type_to_str(keytype));
+               c_write_str(msgbuf);
+               publickey_blob = NULL;
+           }
        } else
            publickey_blob = NULL;
 
@@ -4110,9 +4252,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            if (kbd_inter_running &&
                pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
                /*
-                * This is a further prompt in keyboard-interactive
-                * authentication. Do nothing.
+                * 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 (!gotit)
+                   curr_prompt = 0;
            } else if (pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
                bombout(("Strange packet received during authentication: type %d",
                         pktin.type));
@@ -4186,6 +4333,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            method = 0;
            ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
 
+           /*
+            * 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.
+            */
+           echo = 0;
+
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
                 * Attempt public-key authentication using Pageant.
@@ -4196,6 +4351,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                static int authed = FALSE;
                void *r;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
 
                tried_agent = TRUE;
@@ -4313,10 +4469,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        if (ret) {
                            if (ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
                                logevent("Sending Pageant's response");
-                               ssh2_pkt_addstring_start();
-                               ssh2_pkt_addstring_data(ret + 9,
-                                                       GET_32BIT(ret +
-                                                                 5));
+                               ssh2_add_sigblob(pkblob, pklen,
+                                                ret + 9, GET_32BIT(ret + 5));
                                ssh2_pkt_send();
                                authed = TRUE;
                                break;
@@ -4332,7 +4486,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
            }
 
-           if (!method && can_pubkey && *cfg.keyfile
+           if (!method && can_pubkey && publickey_blob
                && !tried_pubkey_config) {
                unsigned char *pub_blob;
                char *algorithm, *comment;
@@ -4340,6 +4494,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                tried_pubkey_config = TRUE;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
 
                /*
@@ -4394,6 +4549,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
 
                ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
@@ -4414,6 +4570,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
 
                kbd_inter_running = TRUE;
+               curr_prompt = 0;
            }
 
            if (kbd_inter_running) {
@@ -4421,34 +4578,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
 
-               /* We've got packet with that "interactive" info
-                  dump banners, and set its prompt as ours */
-               {
-                   char *name, *inst, *lang, *prompt;
-                   int name_len, inst_len, lang_len, prompt_len;
+               if (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;
+
                    ssh2_pkt_getstring(&name, &name_len);
                    ssh2_pkt_getstring(&inst, &inst_len);
                    ssh2_pkt_getstring(&lang, &lang_len);
-                   if (name_len > 0)
+                   if (name_len > 0) {
                        c_write_untrusted(name, name_len);
-                   if (inst_len > 0)
+                       c_write_str("\r\n");
+                   }
+                   if (inst_len > 0) {
                        c_write_untrusted(inst, inst_len);
+                       c_write_str("\r\n");
+                   }
                    num_prompts = ssh2_pkt_getuint32();
+               }
 
-                   ssh2_pkt_getstring(&prompt, &prompt_len);
-                   strncpy(pwprompt, prompt, sizeof(pwprompt));
-                   pwprompt[prompt_len < sizeof(pwprompt) ?
-                            prompt_len : sizeof(pwprompt)-1] = '\0';
-                   need_pw = TRUE;
+               /*
+                * If there are prompts remaining in the packet,
+                * display one and get a response.
+                */
+               if (curr_prompt < num_prompts) {
+                   char *prompt;
+                   int prompt_len;
 
+                   ssh2_pkt_getstring(&prompt, &prompt_len);
+                   if (prompt_len > 0) {
+                       strncpy(pwprompt, prompt, sizeof(pwprompt));
+                       pwprompt[prompt_len < sizeof(pwprompt) ?
+                                prompt_len : sizeof(pwprompt)-1] = '\0';
+                   } else {
+                       strcpy(pwprompt,
+                              "<server failed to send prompt>: ");
+                   }
                    echo = ssh2_pkt_getbool();
-               }
+                   need_pw = TRUE;
+               } else
+                   need_pw = FALSE;
            }
 
            if (!method && can_passwd) {
                method = AUTH_PASSWORD;
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD;
                sprintf(pwprompt, "%.90s@%.90s's password: ", username,
                        savedhost);
@@ -4477,41 +4658,17 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        crReturnV;
                    }
                } else {
-                   static int pos = 0;
-                   static char c;
-
+                   static int ret;
                    c_write_untrusted(pwprompt, strlen(pwprompt));
                    ssh_send_ok = 1;
 
-                   pos = 0;
-                   while (pos >= 0) {
+                   setup_userpass_input(password, sizeof(password), echo);
+                   do {
                        crWaitUntilV(!ispkt);
-                       while (inlen--)
-                           switch (c = *in++) {
-                             case 10:
-                             case 13:
-                               password[pos] = 0;
-                               pos = -1;
-                               break;
-                             case 8:
-                             case 127:
-                               if (pos > 0)
-                                   pos--;
-                               break;
-                             case 21:
-                             case 27:
-                               pos = 0;
-                               break;
-                             case 3:
-                             case 4:
-                               cleanup_exit(0);
-                               break;
-                             default:
-                               if (pos < sizeof(password)-1)
-                                   password[pos++] = c;
-                               break;
-                           }
-                   }
+                       ret = process_userpass_input(in, inlen);
+                   } while (ret == 0);
+                   if (ret < 0)
+                       cleanup_exit(0);
                    c_write_str("\r\n");
                }
            }
@@ -4539,8 +4696,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    ssh2_pkt_send();
                    type = AUTH_TYPE_NONE;
                } else {
-                   unsigned char *blob, *sigdata;
-                   int blob_len, sigdata_len;
+                   unsigned char *pkblob, *sigblob, *sigdata;
+                   int pkblob_len, sigblob_len, sigdata_len;
 
                    /*
                     * We have loaded the private key and the server
@@ -4553,10 +4710,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    ssh2_pkt_addstring("publickey");    /* method */
                    ssh2_pkt_addbool(TRUE);
                    ssh2_pkt_addstring(key->alg->name);
-                   blob = key->alg->public_blob(key->data, &blob_len);
+                   pkblob = key->alg->public_blob(key->data, &pkblob_len);
                    ssh2_pkt_addstring_start();
-                   ssh2_pkt_addstring_data(blob, blob_len);
-                   sfree(blob);
+                   ssh2_pkt_addstring_data(pkblob, pkblob_len);
 
                    /*
                     * The data to be signed is:
@@ -4572,12 +4728,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    memcpy(sigdata + 4, ssh2_session_id, 20);
                    memcpy(sigdata + 24, pktout.data + 5,
                           pktout.length - 5);
-                   blob =
-                       key->alg->sign(key->data, sigdata, sigdata_len,
-                                      &blob_len);
-                   ssh2_pkt_addstring_start();
-                   ssh2_pkt_addstring_data(blob, blob_len);
-                   sfree(blob);
+                   sigblob = key->alg->sign(key->data, sigdata,
+                                            sigdata_len, &sigblob_len);
+                   ssh2_add_sigblob(pkblob, pkblob_len,
+                                    sigblob, sigblob_len);
+                   sfree(pkblob);
+                   sfree(sigblob);
                    sfree(sigdata);
 
                    ssh2_pkt_send();
@@ -4641,11 +4797,28 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                logevent("Sent password");
                type = AUTH_TYPE_PASSWORD;
            } else if (method == AUTH_KEYBOARD_INTERACTIVE) {
-                ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
-                ssh2_pkt_adduint32(num_prompts);
-                ssh2_pkt_addstring(password);
-                memset(password, 0, sizeof(password));
-                ssh2_pkt_send();
+               if (curr_prompt == 0) {
+                   ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+                   ssh2_pkt_adduint32(num_prompts);
+               }
+               if (need_pw) {         /* only add pw if we just got one! */
+                   ssh2_pkt_addstring(password);
+                   memset(password, 0, sizeof(password));
+                   curr_prompt++;
+               }
+               if (curr_prompt >= num_prompts) {
+                   ssh2_pkt_send();
+               } 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?
+                    */
+                   gotit = TRUE;
+               }
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
            } else {
                c_write_str
@@ -5235,7 +5408,6 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c->type = CHAN_SOCKDATA;
                c->v.v2.remwindow = ssh2_pkt_getuint32();
                c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
-               bufchain_init(&c->v.v2.outbuffer);
                if (c->u.pfd.s)
                    pfd_confirm(c->u.pfd.s);
                if (c->closes) {
@@ -5637,6 +5809,7 @@ void *new_sock_channel(Socket s)
        c->closes = 0;
        c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
        c->u.pfd.s = s;
+       bufchain_init(&c->v.v2.outbuffer);
        add234(ssh_channels, c);
     }
     return c;