Add support for RFC 4432 RSA key exchange, the patch for which has been
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 6fe865b..64a069e 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -1,7 +1,13 @@
+/*
+ * SSH backend.
+ */
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <assert.h>
+#include <limits.h>
+#include <signal.h>
 
 #include "putty.h"
 #include "tree234.h"
@@ -77,6 +83,9 @@
 #define SSH2_MSG_KEX_DH_GEX_GROUP                 31   /* 0x1f */
 #define SSH2_MSG_KEX_DH_GEX_INIT                  32   /* 0x20 */
 #define SSH2_MSG_KEX_DH_GEX_REPLY                 33   /* 0x21 */
+#define SSH2_MSG_KEXRSA_PUBKEY                    30    /* 0x1e */
+#define SSH2_MSG_KEXRSA_SECRET                    31    /* 0x1f */
+#define SSH2_MSG_KEXRSA_DONE                      32    /* 0x20 */
 #define SSH2_MSG_USERAUTH_REQUEST                 50   /* 0x32 */
 #define SSH2_MSG_USERAUTH_FAILURE                 51   /* 0x33 */
 #define SSH2_MSG_USERAUTH_SUCCESS                 52   /* 0x34 */
  */
 #define SSH2_PKTCTX_DHGROUP          0x0001
 #define SSH2_PKTCTX_DHGEX            0x0002
+#define SSH2_PKTCTX_RSAKEX           0x0004
 #define SSH2_PKTCTX_KEX_MASK         0x000F
 #define SSH2_PKTCTX_PUBLICKEY        0x0010
 #define SSH2_PKTCTX_PASSWORD         0x0020
 
 static const char *const ssh2_disconnect_reasons[] = {
     NULL,
-    "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT",
-    "SSH_DISCONNECT_PROTOCOL_ERROR",
-    "SSH_DISCONNECT_KEY_EXCHANGE_FAILED",
-    "SSH_DISCONNECT_HOST_AUTHENTICATION_FAILED",
-    "SSH_DISCONNECT_MAC_ERROR",
-    "SSH_DISCONNECT_COMPRESSION_ERROR",
-    "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE",
-    "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED",
-    "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE",
-    "SSH_DISCONNECT_CONNECTION_LOST",
-    "SSH_DISCONNECT_BY_APPLICATION",
-    "SSH_DISCONNECT_TOO_MANY_CONNECTIONS",
-    "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER",
-    "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE",
-    "SSH_DISCONNECT_ILLEGAL_USER_NAME",
+    "host not allowed to connect",
+    "protocol error",
+    "key exchange failed",
+    "host authentication failed",
+    "MAC error",
+    "compression error",
+    "service not available",
+    "protocol version not supported",
+    "host key not verifiable",
+    "connection lost",
+    "by application",
+    "too many connections",
+    "auth cancelled by user",
+    "no more auth methods available",
+    "illegal user name",
 };
 
 #define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1    /* 0x1 */
@@ -333,6 +343,9 @@ static char *ssh2_pkt_type(int pkt_ctx, int type)
     translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
     translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
     translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
+    translatec(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
+    translatec(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
     translate(SSH2_MSG_USERAUTH_REQUEST);
     translate(SSH2_MSG_USERAUTH_FAILURE);
     translate(SSH2_MSG_USERAUTH_SUCCESS);
@@ -459,13 +472,16 @@ 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[] = {
-    &ssh_sha1, &ssh_md5
+    &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
 const static struct ssh_mac *buggymacs[] = {
-    &ssh_sha1_buggy, &ssh_md5
+    &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
 };
 
 static void *ssh_comp_none_init(void)
@@ -679,7 +695,8 @@ struct ssh_tag {
     const struct plug_function_table *fn;
     /* the above field _must_ be first in the structure */
 
-    SHA_State exhash, exhashbase;
+    char *v_c, *v_s;
+    void *exhash;
 
     Socket s;
 
@@ -704,7 +721,8 @@ struct ssh_tag {
     void *cs_comp_ctx, *sc_comp_ctx;
     const struct ssh_kex *kex;
     const struct ssh_signkey *hostkey;
-    unsigned char v2_session_id[20];
+    unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
+    int v2_session_id_len;
     void *kex_ctx;
 
     char *savedhost;
@@ -719,8 +737,10 @@ struct ssh_tag {
 
     tree234 *channels;                /* indexed by local id */
     struct ssh_channel *mainchan;      /* primary session channel */
+    int ncmode;                               /* is primary channel direct-tcpip? */
     int exitcode;
     int close_expected;
+    int clean_exit;
 
     tree234 *rportfwds, *portfwds;
 
@@ -748,13 +768,7 @@ struct ssh_tag {
      */
     int fallback_cmd;
 
-    /*
-     * Used for username and password input.
-     */
-    char *userpass_input_buffer;
-    int userpass_input_buflen;
-    int userpass_input_bufpos;
-    int userpass_input_echo;
+    bufchain banner;   /* accumulates banners during do_ssh2_authconn */
 
     int pkt_ctx;
 
@@ -1042,27 +1056,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] == '\n' || (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)
@@ -1265,7 +1280,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
      * _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) {
+    if (st->len < 0 || st->len > 35000 || st->pad < 4 ||
+       st->len - st->pad < 1 || (st->len + 4) % st->cipherblk != 0) {
        bombout(("Incoming packet was garbled on decryption"));
        ssh_free_packet(st->pktin);
        crStop(NULL);
@@ -1434,11 +1450,17 @@ static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
     return biglen + 4;         /* len(length+padding+type+data+CRC) */
 }
 
+static int s_write(Ssh ssh, void *data, int len)
+{
+    log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, 0, NULL);
+    return sk_write(ssh->s, (char *)data, len);
+}
+
 static void s_wrpkt(Ssh ssh, struct Packet *pkt)
 {
     int len, backlog, offset;
     len = s_wrpkt_prepare(ssh, pkt, &offset);
-    backlog = sk_write(ssh->s, (char *)pkt->data + offset, len);
+    backlog = s_write(ssh, pkt->data + offset, len);
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
     ssh_free_packet(pkt);
@@ -1475,6 +1497,7 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
 
     while ((argtype = va_arg(ap, int)) != PKT_END) {
        unsigned char *argp, argchar;
+       char *sargp;
        unsigned long argint;
        int arglen;
        switch (argtype) {
@@ -1493,8 +1516,8 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
            ssh_pkt_adddata(pkt, argp, arglen);
            break;
          case PKT_STR:
-           argp = va_arg(ap, unsigned char *);
-           ssh_pkt_addstring(pkt, argp);
+           sargp = va_arg(ap, char *);
+           ssh_pkt_addstring(pkt, sargp);
            break;
          case PKT_BIGNUM:
            bn = va_arg(ap, Bignum);
@@ -1558,21 +1581,21 @@ static int ssh_versioncmp(char *a, char *b)
 
 /*
  * Utility routines for putting an SSH-protocol `string' and
- * `uint32' into a SHA state.
+ * `uint32' into a hash state.
  */
-static void sha_string(SHA_State * s, void *str, int len)
+static void hash_string(const struct ssh_hash *h, void *s, void *str, int len)
 {
     unsigned char lenblk[4];
     PUT_32BIT(lenblk, len);
-    SHA_Bytes(s, lenblk, 4);
-    SHA_Bytes(s, str, len);
+    h->bytes(s, lenblk, 4);
+    h->bytes(s, str, len);
 }
 
-static void sha_uint32(SHA_State * s, unsigned i)
+static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i)
 {
     unsigned char intblk[4];
     PUT_32BIT(intblk, i);
-    SHA_Bytes(s, intblk, 4);
+    h->bytes(s, intblk, 4);
 }
 
 /*
@@ -1582,7 +1605,7 @@ static void ssh_pkt_ensure(struct Packet *pkt, int length)
 {
     if (pkt->maxlen < length) {
        unsigned char *body = pkt->body;
-       int offset = body ? pkt->data - body : 0;
+       int offset = body ? body - pkt->data : 0;
        pkt->maxlen = length + 256;
        pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char);
        if (body) pkt->body = pkt->data + offset;
@@ -1640,7 +1663,7 @@ static void ssh_pkt_addstring(struct Packet *pkt, char *data)
 static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b)
 {
     int len = ssh1_bignum_length(b);
-    unsigned char *data = snewn(len, char);
+    unsigned char *data = snewn(len, unsigned char);
     (void) ssh1_write_bignum(data, b);
     ssh_pkt_adddata(pkt, data, len);
     sfree(data);
@@ -1650,8 +1673,6 @@ static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
     unsigned char *p;
     int i, n = (bignum_bitcount(b) + 7) / 8;
     p = snewn(n + 1, unsigned char);
-    if (!p)
-       fatalbox("out of memory");
     p[0] = 0;
     for (i = 1; i <= n; i++)
        p[i] = bignum_byte(b, n - i);
@@ -1694,10 +1715,10 @@ static struct Packet *ssh1_pkt_init(int pkt_type)
 static struct Packet *ssh2_pkt_init(int pkt_type)
 {
     struct Packet *pkt = ssh_new_packet();
-    pkt->length = 5;
+    pkt->length = 5; /* space for packet length + padding length */
     pkt->forcepad = 0;
     ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
-    pkt->body = pkt->data + pkt->length;
+    pkt->body = pkt->data + pkt->length; /* after packet type */
     return pkt;
 }
 
@@ -1824,7 +1845,7 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
        return;
     }
     len = ssh2_pkt_construct(ssh, pkt);
-    backlog = sk_write(ssh->s, (char *)pkt->data, len);
+    backlog = s_write(ssh, pkt->data, len);
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
 
@@ -1850,6 +1871,7 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
         * get encrypted with a known IV.
         */
        struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+       ssh2_pkt_addstring_start(ipkt);
        ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
     }
     len = ssh2_pkt_construct(ssh, pkt);
@@ -1892,7 +1914,6 @@ static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt)
        ssh2_pkt_send_noqueue(ssh, pkt);
 }
 
-#if 0 /* disused */
 /*
  * Either queue or defer a packet, depending on whether queueing is
  * set.
@@ -1904,7 +1925,6 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
     else
        ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
 }
-#endif
 
 /*
  * Send the whole deferred data block constructed by
@@ -1922,8 +1942,7 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
 static void ssh_pkt_defersend(Ssh ssh)
 {
     int backlog;
-    backlog = sk_write(ssh->s, (char *)ssh->deferred_send_data,
-                      ssh->deferred_len);
+    backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len);
     ssh->deferred_len = ssh->deferred_size = 0;
     sfree(ssh->deferred_send_data);
     ssh->deferred_send_data = NULL;
@@ -1939,6 +1958,74 @@ static void ssh_pkt_defersend(Ssh ssh)
 }
 
 /*
+ * Send a packet whose length needs to be disguised (typically
+ * passwords or keyboard-interactive responses).
+ */
+static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt,
+                                      int padsize)
+{
+#if 0
+    if (0) {
+       /*
+        * The simplest way to do this is to adjust the
+        * variable-length padding field in the outgoing packet.
+        * 
+        * Currently compiled out, because some Cisco SSH servers
+        * don't like excessively padded packets (bah, why's it
+        * always Cisco?)
+        */
+       pkt->forcepad = padsize;
+       ssh2_pkt_send(ssh, pkt);
+    } else
+#endif
+    {
+       /*
+        * If we can't do that, however, an alternative approach is
+        * to use the pkt_defer mechanism to bundle the packet
+        * tightly together with an SSH_MSG_IGNORE such that their
+        * combined length is a constant. So first we construct the
+        * final form of this packet and defer its sending.
+        */
+       ssh2_pkt_defer(ssh, pkt);
+
+       /*
+        * Now construct an SSH_MSG_IGNORE which includes a string
+        * that's an exact multiple of the cipher block size. (If
+        * the cipher is NULL so that the block size is
+        * unavailable, we don't do this trick at all, because we
+        * gain nothing by it.)
+        */
+       if (ssh->cscipher) {
+           int stringlen, i;
+
+           stringlen = (256 - ssh->deferred_len);
+           stringlen += ssh->cscipher->blksize - 1;
+           stringlen -= (stringlen % ssh->cscipher->blksize);
+           if (ssh->cscomp) {
+               /*
+                * Temporarily disable actual compression, so we
+                * can guarantee to get this string exactly the
+                * length we want it. The compression-disabling
+                * routine should return an integer indicating how
+                * many bytes we should adjust our string length
+                * by.
+                */
+               stringlen -=
+                   ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
+           }
+           pkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+           ssh2_pkt_addstring_start(pkt);
+           for (i = 0; i < stringlen; i++) {
+               char c = (char) random_byte();
+               ssh2_pkt_addstring_data(pkt, &c, 1);
+           }
+           ssh2_pkt_defer(ssh, pkt);
+       }
+       ssh_pkt_defersend(ssh);
+    }
+}
+
+/*
  * Send all queued SSH-2 packets. We send them by means of
  * ssh2_pkt_defer_noqueue(), in case they included a pair of
  * packets that needed to be lumped together.
@@ -1970,12 +2057,12 @@ void bndebug(char *string, Bignum b)
 }
 #endif
 
-static void sha_mpint(SHA_State * s, Bignum b)
+static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b)
 {
     unsigned char *p;
     int len;
     p = ssh2_mpint_fmt(b, &len);
-    sha_string(s, p, len);
+    hash_string(h, s, p, len);
     sfree(p);
 }
 
@@ -2155,6 +2242,13 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
 
     ssh->remote_bugs = 0;
 
+    /*
+     * General notes on server version strings:
+     *  - Not all servers reporting "Cisco-1.25" have all the bugs listed
+     *    here -- in particular, we've heard of one that's perfectly happy
+     *    with SSH1_MSG_IGNOREs -- but this string never seems to change,
+     *    so we can't distinguish them.
+     */
     if (ssh->cfg.sshbug_ignore1 == FORCE_ON ||
        (ssh->cfg.sshbug_ignore1 == AUTO &&
         (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
@@ -2244,10 +2338,13 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
 
     if (ssh->cfg.sshbug_rekey2 == FORCE_ON ||
        (ssh->cfg.sshbug_rekey2 == AUTO &&
-        (wc_match("OpenSSH_2.[0-4]*", imp) ||
+        (wc_match("DigiSSH_2.0", imp) ||
+         wc_match("OpenSSH_2.[0-4]*", imp) ||
          wc_match("OpenSSH_2.5.[0-3]*", imp) ||
          wc_match("Sun_SSH_1.0", imp) ||
-         wc_match("Sun_SSH_1.0.1", imp)))) {
+         wc_match("Sun_SSH_1.0.1", imp) ||
+         /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
+         wc_match("WeOnlyDo-*", imp)))) {
        /*
         * These versions have the SSH-2 rekey bug.
         */
@@ -2293,23 +2390,20 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
 
     crBegin(ssh->do_ssh_init_crstate);
 
-    /* Search for the string "SSH-" in the input. */
-    s->i = 0;
-    while (1) {
-       static const int transS[] = { 1, 2, 2, 1 };
-       static const int transH[] = { 0, 0, 3, 0 };
-       static const int transminus[] = { 0, 0, 0, -1 };
-       if (c == 'S')
-           s->i = transS[s->i];
-       else if (c == 'H')
-           s->i = transH[s->i];
-       else if (c == '-')
-           s->i = transminus[s->i];
-       else
-           s->i = 0;
-       if (s->i < 0)
-           break;
-       crReturn(1);                   /* get another character */
+    /* Search for a line beginning with the string "SSH-" in the input. */
+    for (;;) {
+       if (c != 'S') goto no;
+       crReturn(1);
+       if (c != 'S') goto no;
+       crReturn(1);
+       if (c != 'H') goto no;
+       crReturn(1);
+       if (c != '-') goto no;
+       break;
+      no:
+       while (c != '\012')
+           crReturn(1);
+       crReturn(1);
     }
 
     s->vstrsize = 16;
@@ -2383,15 +2477,19 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
         ssh_fix_verstring(verstring);
 
         if (ssh->version == 2) {
+           size_t len;
             /*
              * Hash our version string and their version string.
              */
-            SHA_Init(&ssh->exhashbase);
-            sha_string(&ssh->exhashbase, verstring,
-                       strcspn(verstring, "\015\012"));
-            sha_string(&ssh->exhashbase, s->vstring,
-                       strcspn(s->vstring, "\015\012"));
-
+           len = strcspn(verstring, "\015\012");
+           ssh->v_c = snewn(len + 1, char);
+           memcpy(ssh->v_c, verstring, len);
+           ssh->v_c[len] = 0;
+           len = strcspn(s->vstring, "\015\012");
+           ssh->v_s = snewn(len + 1, char);
+           memcpy(ssh->v_s, s->vstring, len);
+           ssh->v_s[len] = 0;
+           
             /*
              * Initialise SSH-2 protocol.
              */
@@ -2408,7 +2506,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
         }
         logeventf(ssh, "We claim version: %.*s",
                   strcspn(verstring, "\015\012"), verstring);
-       sk_write(ssh->s, verstring, strlen(verstring));
+       s_write(ssh, verstring, strlen(verstring));
         sfree(verstring);
        if (ssh->version == 2)
            do_ssh2_transport(ssh, NULL, -1, NULL);
@@ -2428,7 +2526,9 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
 static void ssh_process_incoming_data(Ssh ssh,
                                      unsigned char **data, int *datalen)
 {
-    struct Packet *pktin = ssh->s_rdpkt(ssh, data, datalen);
+    struct Packet *pktin;
+
+    pktin = ssh->s_rdpkt(ssh, data, datalen);
     if (pktin) {
        ssh->protocol(ssh, NULL, 0, pktin);
        ssh_free_packet(pktin);
@@ -2471,6 +2571,9 @@ static void ssh_set_frozen(Ssh ssh, int frozen)
 
 static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
 {
+    /* Log raw data, if we're in that mode. */
+    log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, 0, NULL);
+
     crBegin(ssh->ssh_gotdata_crstate);
 
     /*
@@ -2496,24 +2599,29 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
      * everything to s_rdpkt, and then pass the resulting packets
      * to the proper protocol handler.
      */
-    if (datalen == 0)
-       crReturnV;
-
-    /*
-     * Process queued data if there is any.
-     */
-    ssh_process_queued_incoming_data(ssh);
 
     while (1) {
-       while (datalen > 0) {
-           if (ssh->frozen)
+       while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) {
+           if (ssh->frozen) {
                ssh_queue_incoming_data(ssh, &data, &datalen);
-
-           ssh_process_incoming_data(ssh, &data, &datalen);
-
+               /* This uses up all data and cannot cause anything interesting
+                * to happen; indeed, for anything to happen at all, we must
+                * return, so break out. */
+               break;
+           } else if (bufchain_size(&ssh->queued_incoming_data) > 0) {
+               /* This uses up some or all data, and may freeze the
+                * session. */
+               ssh_process_queued_incoming_data(ssh);
+           } else {
+               /* This uses up some or all data, and may freeze the
+                * session. */
+               ssh_process_incoming_data(ssh, &data, &datalen);
+           }
+           /* FIXME this is probably EBW. */
            if (ssh->state == SSH_STATE_CLOSED)
                return;
        }
+       /* We're out of data. Go and get some more. */
        crReturnV;
     }
     crFinishV;
@@ -2595,20 +2703,23 @@ static int ssh_closing(Plug plug, const char *error_msg, int error_code,
     Ssh ssh = (Ssh) plug;
     int need_notify = ssh_do_close(ssh, FALSE);
 
-    if (!error_msg && !ssh->close_expected) {
-        error_msg = "Server unexpectedly closed network connection";
+    if (!error_msg) {
+       if (!ssh->close_expected)
+           error_msg = "Server unexpectedly closed network connection";
+       else
+           error_msg = "Server closed network connection";
     }
 
+    if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
+       ssh->exitcode = 0;
+
     if (need_notify)
         notify_remote_exit(ssh->frontend);
 
-    if (error_msg) {
-       /* A socket error has occurred. */
+    if (error_msg)
        logevent(error_msg);
+    if (!ssh->close_expected || !ssh->clean_exit)
        connection_fatal(ssh->frontend, "%s", error_msg);
-    } else {
-        logevent("Server closed network connection");
-    }
     return 0;
 }
 
@@ -2655,8 +2766,6 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     const char *err;
 
     ssh->savedhost = snewn(1 + strlen(host), char);
-    if (!ssh->savedhost)
-       fatalbox("Out of memory");
     strcpy(ssh->savedhost, host);
 
     if (port < 0)
@@ -2741,78 +2850,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;
@@ -2872,6 +2909,39 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
 }
 
 /*
+ * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
+ * non-NULL, otherwise just close the connection. `client_reason' == NULL
+ * => log `wire_reason'.
+ */
+static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
+                          int code, int clean_exit)
+{
+    char *error;
+    if (!client_reason)
+       client_reason = wire_reason;
+    if (client_reason)
+       error = dupprintf("Disconnected: %s", client_reason);
+    else
+       error = dupstr("Disconnected");
+    if (wire_reason) {
+       if (ssh->version == 1) {
+           send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason,
+                       PKT_END);
+       } else if (ssh->version == 2) {
+           struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+           ssh2_pkt_adduint32(pktout, code);
+           ssh2_pkt_addstring(pktout, wire_reason);
+           ssh2_pkt_addstring(pktout, "en");   /* language tag */
+           ssh2_pkt_send_noqueue(ssh, pktout);
+       }
+    }
+    ssh->close_expected = TRUE;
+    ssh->clean_exit = clean_exit;
+    ssh_closing((Plug)ssh, error, 0, 0);
+    sfree(error);
+}
+
+/*
  * Handle the key exchange and user authentication phases.
  */
 static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
@@ -2892,9 +2962,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;
@@ -2976,8 +3046,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
 
     s->rsabuf = snewn(s->len, unsigned char);
-    if (!s->rsabuf)
-       fatalbox("Out of memory");
 
     /*
      * Verify the host key.
@@ -2989,8 +3057,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        int len = rsastr_len(&hostkey);
        char fingerprint[100];
        char *keystr = snewn(len, char);
-       if (!keystr)
-           fatalbox("Out of memory");
        rsastr_fmt(keystr, &hostkey);
        rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
 
@@ -3014,8 +3080,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
         ssh_set_frozen(ssh, 0);
 
         if (s->dlgret == 0) {
-            ssh->close_expected = TRUE;
-            ssh_closing((Plug)ssh, NULL, 0, 0);
+           ssh_disconnect(ssh, "User aborted at host key verification",
+                          NULL, 0, TRUE);
            crStop(0);
         }
     }
@@ -3095,8 +3161,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            }
             ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
         }
@@ -3160,35 +3226,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.
-                    */
-                   logevent("No username provided. Abandoning session.");
-                   ssh->close_expected = TRUE;
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-                   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';
@@ -3196,14 +3260,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);
        }
     }
 
@@ -3216,18 +3280,51 @@ 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))
+       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;
 
     while (pktin->type == SSH1_SMSG_FAILURE) {
        s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
 
-       if (agent_exists() && !s->tried_agent) {
+       if (ssh->cfg.tryagent && agent_exists() && !s->tried_agent) {
            /*
             * Attempt RSA authentication using Pageant.
             */
@@ -3261,13 +3358,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                s->p += 4;
                logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
                for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-                   logeventf(ssh, "Trying Pageant key #%d", s->keyi);
-                   if (s->publickey_blob &&
-                       !memcmp(s->p, s->publickey_blob,
-                               s->publickey_bloblen)) {
-                       logevent("This key matches configured key file");
-                       s->tried_publickey = 1;
-                   }
+                   unsigned char *pkblob = s->p;
                    s->p += 4;
                    {
                        int n, ok = FALSE;
@@ -3300,6 +3391,17 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                            break;
                        }
                    }
+                   if (s->publickey_blob) {
+                       if (!memcmp(pkblob, s->publickey_blob,
+                                   s->publickey_bloblen)) {
+                           logeventf(ssh, "Pageant key #%d matches "
+                                     "configured key file", s->keyi);
+                           s->tried_publickey = 1;
+                       } else
+                           /* Skip non-configured key */
+                           continue;
+                   }
+                   logeventf(ssh, "Trying Pageant key #%d", s->keyi);
                    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
                                PKT_BIGNUM, s->key.modulus, PKT_END);
                    crWaitUntil(pktin);
@@ -3390,12 +3492,156 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                        break;
                }
                sfree(s->response);
+               if (s->publickey_blob && !s->tried_publickey)
+                   logevent("Configured key file not in Pageant");
            }
            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 */
+                   got_passphrase = FALSE;
+                   /* and try again */
+               } else {
+                   assert(0 && "unexpected return from loadrsakey()");
+                   got_passphrase = FALSE;   /* placate optimisers */
+               }
+           }
+
+           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 something else */
+               }
+               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 something else */
+               } 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)) &&
@@ -3413,23 +3659,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 &&
@@ -3447,53 +3701,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);
        }
 
        /*
@@ -3501,251 +3742,153 @@ 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.
                 */
-               send_packet(ssh, SSH1_MSG_DISCONNECT,
-                           PKT_STR, "No more passwords available to try",
-                           PKT_END);
-               logevent("Unable to authenticate");
-               connection_fatal(ssh->frontend, "Unable to authenticate");
-               ssh->close_expected = TRUE;
-                ssh_closing((Plug)ssh, NULL, 0, 0);
-               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 can't deal with SSH1_MSG_IGNORE, at
+            * least in this context. 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.
+            * 
+            * A few servers can deal with neither SSH1_MSG_IGNORE
+            * here _nor_ a padded password string.
+            * For these servers we are left with no defences
+            * against password length sniffing.
             */
-           send_packet(ssh, SSH1_CMSG_AUTH_RSA,
-                       PKT_BIGNUM, s->key.modulus, PKT_END);
+           if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
+               !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+               /*
+                * The server can deal with SSH1_MSG_IGNORE, so
+                * we can use the primary defence.
+                */
+               int bottom, top, pwlen, i;
+               char *randomstr;
 
-           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);
-           }
+               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;
+               }
 
-           {
-               int i;
-               unsigned char buffer[32];
-               Bignum challenge, response;
+               assert(pwlen >= bottom && pwlen <= top);
 
-               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 */
+               randomstr = snewn(top + 1, char);
 
-               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) {
-               /*
-                * 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.
-                */
-               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;
-
-                   pwlen = strlen(s->password);
-                   if (pwlen < 16) {
-                       bottom = 0;    /* zero length passwords are OK! :-) */
-                       top = 15;
+               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 {
-                       bottom = pwlen & ~7;
-                       top = bottom + 7;
-                   }
-
-                   assert(pwlen >= bottom && pwlen <= top);
-
-                   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 (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 is believed unable to cope with
+                * any of our password camouflage methods.
+                */
+               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)
@@ -3757,6 +3900,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);
@@ -4505,7 +4654,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
            /* Data for an agent message. Buffer it. */
            while (len > 0) {
                if (c->u.a.lensofar < 4) {
-                   unsigned int l = min(4 - c->u.a.lensofar, len);
+                   unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
                    memcpy(c->u.a.msglen + c->u.a.lensofar, p,
                           l);
                    p += l;
@@ -4522,7 +4671,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar >= 4 && len > 0) {
                    unsigned int l =
                        min(c->u.a.totallen - c->u.a.lensofar,
-                           len);
+                           (unsigned)len);
                    memcpy(c->u.a.message + c->u.a.lensofar, p,
                           l);
                    p += l;
@@ -4563,8 +4712,7 @@ static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
      * encrypted packet, we close the session once
      * we've sent EXIT_CONFIRMATION.
      */
-    ssh->close_expected = TRUE;
-    ssh_closing((Plug)ssh, NULL, 0, 0);
+    ssh_disconnect(ssh, NULL, NULL, 0, TRUE);
 }
 
 /* Helper function to deal with sending tty modes for REQUEST_PTY */
@@ -4632,14 +4780,23 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
                                       data, sizeof(data), ssh->cfg.x11_auth);
         x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
+       /*
+        * Note that while we blank the X authentication data here, we don't
+        * take any special action to blank the start of an X11 channel,
+        * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+        * without having session blanking enabled is likely to leak your
+        * cookie into the log.
+        */
        if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
            send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-                       PKT_STR, proto, PKT_STR, data,
+                       PKT_STR, proto,
+                       PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER,
                        PKT_INT, x11_get_screen_number(ssh->cfg.x11_display),
                        PKT_END);
        } else {
            send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-                       PKT_STR, proto, PKT_STR, data, PKT_END);
+                       PKT_STR, proto,
+                       PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER, PKT_END);
        }
        do {
            crReturnV;
@@ -4908,27 +5065,30 @@ static int first_in_commasep_string(char *needle, char *haystack, int haylen)
 
 /*
  * SSH-2 key creation method.
+ * (Currently assumes 2 lots of any hash are sufficient to generate
+ * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
  */
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
-                      unsigned char *sessid, char chr,
+#define SSH2_MKKEY_ITERS (2)
+static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
                       unsigned char *keyspace)
 {
-    SHA_State s;
-    /* First 20 bytes. */
-    SHA_Init(&s);
+    const struct ssh_hash *h = ssh->kex->hash;
+    void *s;
+    /* First hlen bytes. */
+    s = h->init();
     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);
+       hash_mpint(h, s, K);
+    h->bytes(s, H, h->hlen);
+    h->bytes(s, &chr, 1);
+    h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
+    h->final(s, keyspace);
+    /* Next hlen bytes. */
+    s = h->init();
     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);
+       hash_mpint(h, s, K);
+    h->bytes(s, H, h->hlen);
+    h->bytes(s, keyspace, h->hlen);
+    h->final(s, keyspace + h->hlen);
 }
 
 /*
@@ -4941,6 +5101,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     struct do_ssh2_transport_state {
        int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
        Bignum p, g, e, f, K;
+       void *our_kexinit;
+       int our_kexinitlen;
        int kex_init_value, kex_reply_value;
        const struct ssh_mac **maclist;
        int nmacs;
@@ -4950,12 +5112,13 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        const struct ssh_mac *scmac_tobe;
        const struct ssh_compress *cscomp_tobe;
        const struct ssh_compress *sccomp_tobe;
-       char *hostkeydata, *sigdata, *keystr, *fingerprint;
-       int hostkeylen, siglen;
+       char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
+       int hostkeylen, siglen, rsakeylen;
        void *hkey;                    /* actual host key */
-       unsigned char exchange_hash[20];
+       void *rsakey;                  /* for RSA kex */
+       unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
-       const struct ssh_kex *preferred_kex[KEX_MAX];
+       const struct ssh_kexes *preferred_kex[KEX_MAX];
        int n_preferred_ciphers;
        const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
        const struct ssh_compress *preferred_comp;
@@ -5006,7 +5169,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                s->preferred_kex[s->n_preferred_kex++] =
                    &ssh_diffiehellman_group1;
                break;
-             case CIPHER_WARN:
+             case KEX_RSA:
+               s->preferred_kex[s->n_preferred_kex++] =
+                   &ssh_rsa_kex;
+               break;
+             case KEX_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
                if (i < KEX_MAX - 1) {
@@ -5078,12 +5245,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        ssh2_pkt_addstring_start(s->pktout);
        commalist_started = 0;
        for (i = 0; i < s->n_preferred_kex; i++) {
-           const struct ssh_kex *k = s->preferred_kex[i];
+           const struct ssh_kexes *k = s->preferred_kex[i];
            if (!k) continue;          /* warning flag */
-           if (commalist_started)
-               ssh2_pkt_addstring_str(s->pktout, ",");
-           ssh2_pkt_addstring_str(s->pktout, s->preferred_kex[i]->name);
-           commalist_started = 1;
+           for (j = 0; j < k->nkexes; j++) {
+               if (commalist_started)
+                   ssh2_pkt_addstring_str(s->pktout, ",");
+               ssh2_pkt_addstring_str(s->pktout, k->list[j]->name);
+               commalist_started = 1;
+           }
        }
        /* List server host key algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
@@ -5164,15 +5333,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        ssh2_pkt_adduint32(s->pktout, 0);
     }
 
-    ssh->exhash = ssh->exhashbase;
-    sha_string(&ssh->exhash, s->pktout->data + 5, s->pktout->length - 5);
+    s->our_kexinitlen = s->pktout->length - 5;
+    s->our_kexinit = snewn(s->our_kexinitlen, unsigned char);
+    memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); 
 
     ssh2_pkt_send_noqueue(ssh, s->pktout);
 
     if (!pktin)
        crWaitUntil(pktin);
-    if (pktin->length > 5)
-       sha_string(&ssh->exhash, pktin->data + 5, pktin->length - 5);
 
     /*
      * Now examine the other side's KEXINIT to see what we're up
@@ -5201,13 +5369,17 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
        preferred = NULL;
        for (i = 0; i < s->n_preferred_kex; i++) {
-           const struct ssh_kex *k = s->preferred_kex[i];
+           const struct ssh_kexes *k = s->preferred_kex[i];
            if (!k) {
                s->warn_kex = TRUE;
            } else {
-               if (!preferred) preferred = k->name;
-               if (in_commasep_string(k->name, str, len))
-                   ssh->kex = k;
+               for (j = 0; j < k->nkexes; j++) {
+                   if (!preferred) preferred = k->list[j]->name;
+                   if (in_commasep_string(k->list[j]->name, str, len)) {
+                       ssh->kex = k->list[j];
+                       break;
+                   }
+               }
            }
            if (ssh->kex)
                break;
@@ -5330,8 +5502,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at kex warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
@@ -5355,8 +5527,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
@@ -5380,16 +5552,28 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
 
+       ssh->exhash = ssh->kex->hash->init();
+       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
+       hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
+       hash_string(ssh->kex->hash, ssh->exhash,
+           s->our_kexinit, s->our_kexinitlen);
+       sfree(s->our_kexinit);
+       if (pktin->length > 5)
+           hash_string(ssh->kex->hash, ssh->exhash,
+               pktin->data + 5, pktin->length - 5);
+
        if (s->ignorepkt) /* first_kex_packet_follows */
            crWaitUntil(pktin);                /* Ignore packet */
     }
 
+    if (ssh->kex->main_type == KEXTYPE_DH) {
+       /* XXX The lines below should be reindented before this is committed.*/
     /*
      * Work out the number of bits of key we will need from the key
      * exchange. We start with the maximum key length of either
@@ -5402,10 +5586,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        scbits = s->sccipher_tobe->keylen;
        s->nbits = (csbits > scbits ? csbits : scbits);
     }
-    /* The keys only have 160-bit entropy, since they're based on
-     * a SHA-1 hash. So cap the key size at 160 bits. */
-    if (s->nbits > 160)
-       s->nbits = 160;
+    /* The keys only have hlen-bit entropy, since they're based on
+     * a hash. So cap the key size at hlen bits. */
+    if (s->nbits > ssh->kex->hash->hlen * 8)
+       s->nbits = ssh->kex->hash->hlen * 8;
 
     /*
      * If we're doing Diffie-Hellman group exchange, start by
@@ -5446,7 +5630,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                  ssh->kex->groupname);
     }
 
-    logevent("Doing Diffie-Hellman key exchange");
+    logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s",
+             ssh->kex->hash->text_name);
     /*
      * Now generate and send e for Diffie-Hellman.
      */
@@ -5464,6 +5649,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
     ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+    s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
     s->f = ssh2_pkt_getmp(pktin);
     if (!s->f) {
        bombout(("unable to parse key exchange reply packet"));
@@ -5477,29 +5663,139 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * involve user interaction. */
     set_busy_status(ssh->frontend, BUSY_NOT);
 
-    sha_string(&ssh->exhash, s->hostkeydata, s->hostkeylen);
-    if (ssh->kex == &ssh_diffiehellman_gex) {
-       sha_uint32(&ssh->exhash, s->pbits);
-       sha_mpint(&ssh->exhash, s->p);
-       sha_mpint(&ssh->exhash, s->g);
+    hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+    if (!ssh->kex->pdata) {
+       hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
+       hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
+       hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
     }
-    sha_mpint(&ssh->exhash, s->e);
-    sha_mpint(&ssh->exhash, s->f);
-    sha_mpint(&ssh->exhash, s->K);
-    SHA_Final(&ssh->exhash, s->exchange_hash);
+    hash_mpint(ssh->kex->hash, ssh->exhash, s->e);
+    hash_mpint(ssh->kex->hash, ssh->exhash, s->f);
 
     dh_cleanup(ssh->kex_ctx);
+    freebn(s->f);
+    if (!ssh->kex->pdata) {
+        freebn(s->g);
+       freebn(s->p);
+    }
+        /* XXX end incorrectly-indented section */
+    } else {
+       logeventf(ssh, "Doing RSA key exchange with hash %s",
+                 ssh->kex->hash->text_name);
+       ssh->pkt_ctx |= SSH2_PKTCTX_RSAKEX;
+        /*
+         * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+         * from the server.
+         */
+        crWaitUntil(pktin);
+        if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+            bombout(("expected RSA public key packet from server"));
+            crStop(0);
+        }
+
+        ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+        hash_string(ssh->kex->hash, ssh->exhash,
+                   s->hostkeydata, s->hostkeylen);
+       s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+
+        {
+            char *keydata;
+            ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen);
+            s->rsakeydata = snewn(s->rsakeylen, char);
+            memcpy(s->rsakeydata, keydata, s->rsakeylen);
+        }
+
+        s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen);
+        if (!s->rsakey) {
+            sfree(s->rsakeydata);
+            bombout(("unable to parse RSA public key from server"));
+            crStop(0);
+        }
+
+        hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
+
+        /*
+         * Next, set up a shared secret K, of precisely KLEN -
+         * 2*HLEN - 49 bits, where KLEN is the bit length of the
+         * RSA key modulus and HLEN is the bit length of the hash
+         * we're using.
+         */
+        {
+            int klen = ssh_rsakex_klen(s->rsakey);
+            int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49);
+            int i, byte = 0;
+            unsigned char *kstr1, *kstr2, *outstr;
+            int kstr1len, kstr2len, outstrlen;
+
+            s->K = bn_power_2(nbits - 1);
+
+            for (i = 0; i < nbits; i++) {
+                if ((i & 7) == 0) {
+                    byte = random_byte();
+                }
+                bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1);
+            }
+
+            /*
+             * Encode this as an mpint.
+             */
+            kstr1 = ssh2_mpint_fmt(s->K, &kstr1len);
+            kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char);
+            PUT_32BIT(kstr2, kstr1len);
+            memcpy(kstr2 + 4, kstr1, kstr1len);
+
+            /*
+             * Encrypt it with the given RSA key.
+             */
+            outstrlen = (klen + 7) / 8;
+            outstr = snewn(outstrlen, unsigned char);
+            ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len,
+                              outstr, outstrlen, s->rsakey);
+
+            /*
+             * And send it off in a return packet.
+             */
+            s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET);
+            ssh2_pkt_addstring_start(s->pktout);
+            ssh2_pkt_addstring_data(s->pktout, outstr, outstrlen);
+            ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+           hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen);
+
+            sfree(kstr2);
+            sfree(kstr1);
+            sfree(outstr);
+        }
+
+        ssh_rsakex_freekey(s->rsakey);
+
+        crWaitUntil(pktin);
+        if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+            sfree(s->rsakeydata);
+            bombout(("expected signature packet from server"));
+            crStop(0);
+        }
+
+        ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+        sfree(s->rsakeydata);
+    }
+
+    hash_mpint(ssh->kex->hash, ssh->exhash, s->K);
+    assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash));
+    ssh->kex->hash->final(ssh->exhash, s->exchange_hash);
+
     ssh->kex_ctx = NULL;
 
 #if 0
     debug(("Exchange hash is:\n"));
-    dmemdump(s->exchange_hash, 20);
+    dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
 #endif
 
-    s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
     if (!s->hkey ||
        !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
-                                (char *)s->exchange_hash, 20)) {
+                                (char *)s->exchange_hash,
+                                ssh->kex->hash->hlen)) {
        bombout(("Server's host key did not match the signature supplied"));
        crStop(0);
     }
@@ -5529,8 +5825,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     ssh_set_frozen(ssh, 0);
     if (s->dlgret == 0) {
-        ssh->close_expected = TRUE;
-        ssh_closing((Plug)ssh, NULL, 0, 0);
+       ssh_disconnect(ssh, "User aborted at host key verification", NULL,
+                      0, TRUE);
         crStop(0);
     }
     if (!s->got_session_id) {     /* don't bother logging this in rekeys */
@@ -5547,8 +5843,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * authentication.
      */
     if (!s->got_session_id) {
+       assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id));
        memcpy(ssh->v2_session_id, s->exchange_hash,
               sizeof(s->exchange_hash));
+       ssh->v2_session_id_len = ssh->kex->hash->hlen;
+       assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id));
        s->got_session_id = TRUE;
     }
 
@@ -5583,13 +5882,21 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * hash from the _first_ key exchange.
      */
     {
-       unsigned char keyspace[40];
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'C',keyspace);
+       unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+       assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
+       assert((ssh->cscipher->keylen+7) / 8 <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'A',keyspace);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
+       assert(ssh->cscipher->blksize <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'E',keyspace);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
+       assert(ssh->csmac->len <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
+       memset(keyspace, 0, sizeof(keyspace));
     }
 
     logeventf(ssh, "Initialised %.200s client->server encryption",
@@ -5641,13 +5948,21 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * hash from the _first_ key exchange.
      */
     {
-       unsigned char keyspace[40];
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'D',keyspace);
+       unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+       assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
+       assert((ssh->sccipher->keylen+7) / 8 <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'B',keyspace);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
+       assert(ssh->sccipher->blksize <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'F',keyspace);
+       ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
+       assert(ssh->scmac->len <=
+              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
+       memset(keyspace, 0, sizeof(keyspace));
     }
     logeventf(ssh, "Initialised %.200s server->client encryption",
              ssh->sccipher->text_name);
@@ -5658,14 +5973,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                  ssh->sccomp->text_name);
 
     /*
-     * Free key exchange data.
+     * Free shared secret.
      */
-    freebn(s->f);
     freebn(s->K);
-    if (ssh->kex == &ssh_diffiehellman_gex) {
-       freebn(s->g);
-       freebn(s->p);
-    }
 
     /*
      * Key exchange is over. Loop straight back round if we have a
@@ -5696,7 +6006,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * it would only confuse the layer above.
      */
     if (s->activated_authconn) {
-       crReturn(1);
+       crReturn(0);
     }
     s->activated_authconn = TRUE;
 
@@ -5892,7 +6202,8 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
          case CHAN_AGENT:
            while (length > 0) {
                if (c->u.a.lensofar < 4) {
-                   unsigned int l = min(4 - c->u.a.lensofar, length);
+                   unsigned int l = min(4 - c->u.a.lensofar,
+                                        (unsigned)length);
                    memcpy(c->u.a.msglen + c->u.a.lensofar,
                           data, l);
                    data += l;
@@ -5909,7 +6220,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar >= 4 && length > 0) {
                    unsigned int l =
                        min(c->u.a.totallen - c->u.a.lensofar,
-                           length);
+                           (unsigned)length);
                    memcpy(c->u.a.message + c->u.a.lensofar,
                           data, l);
                    data += l;
@@ -6011,8 +6322,6 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
      * not running in -N mode.)
      */
     if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) {
-       logevent("All channels closed. Disconnecting");
-#if 0
        /*
         * We used to send SSH_MSG_DISCONNECT here,
         * because I'd believed that _every_ conforming
@@ -6024,14 +6333,7 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
         * this is more polite than sending a
         * DISCONNECT. So now we don't.
         */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-       ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION);
-       ssh2_pkt_addstring(s->pktout, "All open channels closed");
-       ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-       ssh2_pkt_send_noqueue(ssh, s->pktout);
-#endif
-       ssh->close_expected = TRUE;
-       ssh_closing((Plug)ssh, NULL, 0, 0);
+       ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
     }
 }
 
@@ -6118,18 +6420,10 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
      */
     c = find234(ssh->channels, &localid, ssh_channelfind);
     if (!c) {
-       char buf[80];
-       sprintf(buf, "Received channel request for nonexistent"
-               " channel %d", localid);
-       logevent(buf);
-       pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-       ssh2_pkt_adduint32(pktout, SSH2_DISCONNECT_BY_APPLICATION);
-       ssh2_pkt_addstring(pktout, buf);
-       ssh2_pkt_addstring(pktout, "en");       /* language tag */
-       ssh2_pkt_send_noqueue(ssh, pktout);
-       connection_fatal(ssh->frontend, "%s", buf);
-       ssh->close_expected = TRUE;
-       ssh_closing((Plug)ssh, NULL, 0, 0);
+       char *buf = dupprintf("Received channel request for nonexistent"
+                             " channel %d", localid);
+       ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+       sfree(buf);
        return;
     }
 
@@ -6195,11 +6489,13 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                        is_plausible = FALSE;
                }
            }
+           ssh->exitcode = 128;       /* means `unknown signal' */
            if (is_plausible) {
                if (is_int) {
                    /* Old non-standard OpenSSH. */
                    int signum = ssh_pkt_getuint32(pktin);
                    fmt_sig = dupprintf(" %d", signum);
+                   ssh->exitcode = 128 + signum;
                } else {
                    /* As per the drafts. */
                    char *sig;
@@ -6211,6 +6507,60 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                        fmt_sig = dupprintf(" \"%.*s\"",
                                            siglen, sig);
                    }
+
+                   /*
+                    * Really hideous method of translating the
+                    * signal description back into a locally
+                    * meaningful number.
+                    */
+
+                   if (0)
+                       ;
+#define TRANSLATE_SIGNAL(s) \
+    else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \
+        ssh->exitcode = 128 + SIG ## s
+#ifdef SIGABRT
+                   TRANSLATE_SIGNAL(ABRT);
+#endif
+#ifdef SIGALRM
+                   TRANSLATE_SIGNAL(ALRM);
+#endif
+#ifdef SIGFPE
+                   TRANSLATE_SIGNAL(FPE);
+#endif
+#ifdef SIGHUP
+                   TRANSLATE_SIGNAL(HUP);
+#endif
+#ifdef SIGILL
+                   TRANSLATE_SIGNAL(ILL);
+#endif
+#ifdef SIGINT
+                   TRANSLATE_SIGNAL(INT);
+#endif
+#ifdef SIGKILL
+                   TRANSLATE_SIGNAL(KILL);
+#endif
+#ifdef SIGPIPE
+                   TRANSLATE_SIGNAL(PIPE);
+#endif
+#ifdef SIGQUIT
+                   TRANSLATE_SIGNAL(QUIT);
+#endif
+#ifdef SIGSEGV
+                   TRANSLATE_SIGNAL(SEGV);
+#endif
+#ifdef SIGTERM
+                   TRANSLATE_SIGNAL(TERM);
+#endif
+#ifdef SIGUSR1
+                   TRANSLATE_SIGNAL(USR1);
+#endif
+#ifdef SIGUSR2
+                   TRANSLATE_SIGNAL(USR2);
+#endif
+#undef TRANSLATE_SIGNAL
+                   else
+                       ssh->exitcode = 128;
                }
                core = ssh2_pkt_getbool(pktin);
                ssh_pkt_getstring(pktin, &msg, &msglen);
@@ -6339,7 +6689,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
            }
        }
     } else if (typelen == 22 &&
-              !memcmp(type, "auth-agent@openssh.com", 3)) {
+              !memcmp(type, "auth-agent@openssh.com", 22)) {
        if (!ssh->agentfwd_enabled)
            error = "Agent forwarding is not enabled";
        else {
@@ -6378,6 +6728,21 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     }
 }
 
+/*
+ * Buffer banner messages for later display at some convenient point.
+ */
+static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
+{
+    /* Arbitrary limit to prevent unbounded inflation of buffer */
+    if (bufchain_size(&ssh->banner) <= 131072) {
+       char *banner = NULL;
+       int size = 0;
+       ssh_pkt_getstring(pktin, &banner, &size);
+       if (banner)
+           bufchain_add(&ssh->banner, banner, size);
+    }
+}
+
 /* Helper function to deal with sending tty modes for "pty-req" */
 static void ssh2_send_ttymode(void *data, char *mode, char *val)
 {
@@ -6406,11 +6771,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 {
     struct do_ssh2_authconn_state {
        enum {
-           AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
-               AUTH_PASSWORD,
-               AUTH_KEYBOARD_INTERACTIVE
-       } method;
-       enum {
            AUTH_TYPE_NONE,
                AUTH_TYPE_PUBLICKEY,
                AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
@@ -6419,21 +6779,25 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                AUTH_TYPE_KEYBOARD_INTERACTIVE,
                AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
        } type;
+       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 tried_pubkey_config, done_agent;
+       int kbd_inter_refused;
        int we_are_in;
-       int num_prompts, curr_prompt, echo;
+       prompts_t *cur_prompt;
+       int num_prompts;
        char username[100];
+       char *password;
        int got_username;
-       char pwprompt[512];
-       char password[100];
        void *publickey_blob;
        int publickey_bloblen;
-       unsigned char request[5], *response, *p;
-       int responselen;
+       int publickey_encrypted;
+       char *publickey_algorithm;
+       char *publickey_comment;
+       unsigned char agent_request[5], *agent_response, *agentp;
+       int agent_responselen;
+       unsigned char *pkblob_in_agent;
        int keyi, nkeys;
-       int authed;
        char *pkblob, *alg, *commentp;
        int pklen, alglen, commentlen;
        int siglen, retlen, len;
@@ -6446,16 +6810,153 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
     crBegin(ssh->do_ssh2_authconn_crstate);
 
+    s->done_service_req = FALSE;
+    s->we_are_in = FALSE;
+    if (!ssh->cfg.ssh_no_userauth) {
+       /*
+        * Request userauth protocol, and await a response to it.
+        */
+       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+       ssh2_pkt_addstring(s->pktout, "ssh-userauth");
+       ssh2_pkt_send(ssh, s->pktout);
+       crWaitUntilV(pktin);
+       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
+           s->done_service_req = TRUE;
+    }
+    if (!s->done_service_req) {
+       /*
+        * Request connection protocol directly, without authentication.
+        */
+       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+       ssh2_pkt_addstring(s->pktout, "ssh-connection");
+       ssh2_pkt_send(ssh, s->pktout);
+       crWaitUntilV(pktin);
+       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
+           s->we_are_in = TRUE; /* no auth required */
+       } else {
+           bombout(("Server refused service request"));
+           crStopV;
+       }
+    }
+
+    /* Arrange to be able to deal with any BANNERs that come in.
+     * (We do this now as packets may come in during the next bit.) */
+    bufchain_init(&ssh->banner);
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
+       ssh2_msg_userauth_banner;
+
     /*
-     * Request userauth protocol, and await a response to it.
+     * Misc one-time setup for authentication.
      */
-    s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
-    ssh2_pkt_addstring(s->pktout, "ssh-userauth");
-    ssh2_pkt_send(ssh, s->pktout);
-    crWaitUntilV(pktin);
-    if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
-       bombout(("Server refused user authentication protocol"));
-       crStopV;
+    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;
+           }
+       }
+
+       /*
+        * Find out about any keys Pageant has (but if there's a
+        * public key configured, filter out all others).
+        */
+       s->nkeys = 0;
+       s->agent_response = NULL;
+       s->pkblob_in_agent = NULL;
+       if (ssh->cfg.tryagent && agent_exists()) {
+
+           void *r;
+
+           logevent("Pageant is running. Requesting keys.");
+
+           /* Request the keys held by the agent. */
+           PUT_32BIT(s->agent_request, 1);
+           s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+           if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
+                            ssh_agent_callback, ssh)) {
+               do {
+                   crReturnV;
+                   if (pktin) {
+                       bombout(("Unexpected data from server while"
+                                " waiting for agent response"));
+                       crStopV;
+                   }
+               } while (pktin || inlen > 0);
+               r = ssh->agent_response;
+               s->agent_responselen = ssh->agent_response_len;
+           }
+           s->agent_response = (unsigned char *) r;
+           if (s->agent_response && s->agent_responselen >= 5 &&
+               s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+               int keyi;
+               unsigned char *p;
+               p = s->agent_response + 5;
+               s->nkeys = GET_32BIT(p);
+               p += 4;
+               logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
+               if (s->publickey_blob) {
+                   /* See if configured key is in agent. */
+                   for (keyi = 0; keyi < s->nkeys; keyi++) {
+                       s->pklen = GET_32BIT(p);
+                       if (s->pklen == s->publickey_bloblen &&
+                           !memcmp(p+4, s->publickey_blob,
+                                   s->publickey_bloblen)) {
+                           logeventf(ssh, "Pageant key #%d matches "
+                                     "configured key file", keyi);
+                           s->keyi = keyi;
+                           s->pkblob_in_agent = p;
+                           break;
+                       }
+                       p += 4 + s->pklen;
+                       p += GET_32BIT(p) + 4; /* comment */
+                   }
+                   if (!s->pkblob_in_agent) {
+                       logevent("Configured key file not in Pageant");
+                       s->nkeys = 0;
+                   }
+               }
+           }
+       }
+
     }
 
     /*
@@ -6484,7 +6985,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     s->username[0] = '\0';
     s->got_username = FALSE;
-    do {
+    while (!s->we_are_in) {
        /*
         * Get a username.
         */
@@ -6495,32 +6996,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.
-                    */
-                   logevent("No username provided. Abandoning session.");
-                   ssh->close_expected = TRUE;
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-                   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;
+           }
+           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;
            }
-           s->username[strcspn(s->username, "\n\r")] = '\0';
+           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));
@@ -6550,33 +7050,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        s->we_are_in = FALSE;
 
        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);
+
+       /* Reset agent request state. */
+       s->done_agent = FALSE;
+       if (s->agent_response) {
+           if (s->pkblob_in_agent) {
+               s->agentp = s->pkblob_in_agent;
            } 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;
+               s->agentp = s->agent_response + 5 + 4;
+               s->keyi = 0;
            }
-       } else
-           s->publickey_blob = NULL;
+       }
 
        while (1) {
            /*
@@ -6584,9 +7069,14 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             */
            if (!s->gotit)
                crWaitUntilV(pktin);
-           while (pktin->type == SSH2_MSG_USERAUTH_BANNER) {
-               char *banner;
-               int size;
+           /*
+            * Now is a convenient point to spew any banner material
+            * that we've accumulated. (This should ensure that when
+            * we exit the auth loop, we haven't any left to deal
+            * with.)
+            */
+           {
+               int size = bufchain_size(&ssh->banner);
                /*
                 * Don't show the banner if we're operating in
                 * non-verbose non-interactive mode. (It's probably
@@ -6595,12 +7085,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                 * the banner will screw up processing on the
                 * output of (say) plink.)
                 */
-               if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
-                   ssh_pkt_getstring(pktin, &banner, &size);
-                   if (banner)
-                       c_write_untrusted(ssh, banner, size);
+               if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+                   char *banner = snewn(size, char);
+                   bufchain_fetch(&ssh->banner, banner, size);
+                   c_write_untrusted(ssh, banner, size);
+                   sfree(banner);
                }
-               crWaitUntilV(pktin);
+               bufchain_clear(&ssh->banner);
            }
            if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                logevent("Access granted");
@@ -6608,24 +7099,9 @@ 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) {
-               /* FIXME: perhaps we should support this? */
-               bombout(("PASSWD_CHANGEREQ not yet supported"));
-               crStopV;
-           } else if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
-               bombout(("Strange packet received during authentication: type %d",
-                        pktin->type));
+           if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
+               bombout(("Strange packet received during authentication: "
+                        "type %d", pktin->type));
                crStopV;
            }
 
@@ -6640,7 +7116,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
@@ -6697,418 +7172,275 @@ 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 && !s->done_agent && s->nkeys) {
 
-           if (!s->method && s->can_pubkey &&
-               agent_exists() && !s->tried_agent) {
                /*
-                * Attempt public-key authentication using Pageant.
+                * Attempt public-key authentication using a key from Pageant.
                 */
-               void *r;
-               s->authed = FALSE;
 
                ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
 
-               s->tried_agent = TRUE;
+               logeventf(ssh, "Trying Pageant key #%d", s->keyi);
+
+               /* Unpack key from agent response */
+               s->pklen = GET_32BIT(s->agentp);
+               s->agentp += 4;
+               s->pkblob = (char *)s->agentp;
+               s->agentp += s->pklen;
+               s->alglen = GET_32BIT(s->pkblob);
+               s->alg = s->pkblob + 4;
+               s->commentlen = GET_32BIT(s->agentp);
+               s->agentp += 4;
+               s->commentp = (char *)s->agentp;
+               s->agentp += s->commentlen;
+               /* s->agentp now points at next key, if any */
+
+               /* See if server will accept it */
+               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_start(s->pktout);
+               ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+               ssh2_pkt_addstring_start(s->pktout);
+               ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+               ssh2_pkt_send(ssh, s->pktout);
+               s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
 
-               logevent("Pageant is running. Requesting keys.");
+               crWaitUntilV(pktin);
+               if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
 
-               /* Request the keys held by the agent. */
-               PUT_32BIT(s->request, 1);
-               s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-               if (!agent_query(s->request, 5, &r, &s->responselen,
-                                ssh_agent_callback, ssh)) {
-                   do {
-                       crReturnV;
-                       if (pktin) {
-                           bombout(("Unexpected data from server while"
-                                    " waiting for agent response"));
-                           crStopV;
-                       }
-                   } while (pktin || inlen > 0);
-                   r = ssh->agent_response;
-                   s->responselen = ssh->agent_response_len;
-               }
-               s->response = (unsigned char *) r;
-               if (s->response && s->responselen >= 5 &&
-                   s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
-                   s->p = s->response + 5;
-                   s->nkeys = GET_32BIT(s->p);
-                   s->p += 4;
-                   logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
-                   for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-                       void *vret;
+                   /* Offer of key refused. */
+                   s->gotit = TRUE;
 
-                       logeventf(ssh, "Trying Pageant key #%d", s->keyi);
-                       s->pklen = GET_32BIT(s->p);
-                       s->p += 4;
-                       if (s->publickey_blob &&
-                           s->pklen == s->publickey_bloblen &&
-                           !memcmp(s->p, s->publickey_blob,
-                                   s->publickey_bloblen)) {
-                           logevent("This key matches configured key file");
-                           s->tried_pubkey_config = 1;
-                       }
-                       s->pkblob = (char *)s->p;
-                       s->p += s->pklen;
-                       s->alglen = GET_32BIT(s->pkblob);
-                       s->alg = s->pkblob + 4;
-                       s->commentlen = GET_32BIT(s->p);
-                       s->p += 4;
-                       s->commentp = (char *)s->p;
-                       s->p += s->commentlen;
-                       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_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
-                       ssh2_pkt_send(ssh, s->pktout);
-
-                       crWaitUntilV(pktin);
-                       if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
-                           logevent("Key refused");
-                           continue;
-                       }
+               } else {
+                   
+                   void *vret;
 
-                       if (flags & FLAG_VERBOSE) {
-                           c_write_str(ssh, "Authenticating with "
-                                       "public key \"");
-                           c_write(ssh, s->commentp, s->commentlen);
-                           c_write_str(ssh, "\" from agent\r\n");
-                       }
+                   if (flags & FLAG_VERBOSE) {
+                       c_write_str(ssh, "Authenticating with "
+                                   "public key \"");
+                       c_write(ssh, s->commentp, s->commentlen);
+                       c_write_str(ssh, "\" from agent\r\n");
+                   }
 
-                       /*
-                        * Server is willing to accept the key.
-                        * Construct a SIGN_REQUEST.
-                        */
-                       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, TRUE);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
-                       ssh2_pkt_addstring_start(s->pktout);
-                       ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
-
-                       s->siglen = s->pktout->length - 5 + 4 + 20;
-                        if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                            s->siglen -= 4;
-                       s->len = 1;       /* message type */
-                       s->len += 4 + s->pklen; /* key blob */
-                       s->len += 4 + s->siglen;        /* data to sign */
-                       s->len += 4;      /* flags */
-                       s->agentreq = snewn(4 + s->len, char);
-                       PUT_32BIT(s->agentreq, s->len);
-                       s->q = s->agentreq + 4;
-                       *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
-                       PUT_32BIT(s->q, s->pklen);
-                       s->q += 4;
-                       memcpy(s->q, s->pkblob, s->pklen);
-                       s->q += s->pklen;
-                       PUT_32BIT(s->q, s->siglen);
+                   /*
+                    * Server is willing to accept the key.
+                    * Construct a SIGN_REQUEST.
+                    */
+                   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, TRUE);  /* signature included */
+                   ssh2_pkt_addstring_start(s->pktout);
+                   ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+                   ssh2_pkt_addstring_start(s->pktout);
+                   ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+
+                   /* Ask agent for signature. */
+                   s->siglen = s->pktout->length - 5 + 4 +
+                       ssh->v2_session_id_len;
+                   if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+                       s->siglen -= 4;
+                   s->len = 1;       /* message type */
+                   s->len += 4 + s->pklen;     /* key blob */
+                   s->len += 4 + s->siglen;    /* data to sign */
+                   s->len += 4;      /* flags */
+                   s->agentreq = snewn(4 + s->len, char);
+                   PUT_32BIT(s->agentreq, s->len);
+                   s->q = s->agentreq + 4;
+                   *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+                   PUT_32BIT(s->q, s->pklen);
+                   s->q += 4;
+                   memcpy(s->q, s->pkblob, s->pklen);
+                   s->q += s->pklen;
+                   PUT_32BIT(s->q, s->siglen);
+                   s->q += 4;
+                   /* Now the data to be signed... */
+                   if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+                       PUT_32BIT(s->q, ssh->v2_session_id_len);
                        s->q += 4;
-                       /* Now the data to be signed... */
-                        if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
-                            PUT_32BIT(s->q, 20);
-                            s->q += 4;
-                        }
-                       memcpy(s->q, ssh->v2_session_id, 20);
-                       s->q += 20;
-                       memcpy(s->q, s->pktout->data + 5,
-                              s->pktout->length - 5);
-                       s->q += s->pktout->length - 5;
-                       /* And finally the (zero) flags word. */
-                       PUT_32BIT(s->q, 0);
-                       if (!agent_query(s->agentreq, s->len + 4,
-                                        &vret, &s->retlen,
-                                        ssh_agent_callback, ssh)) {
-                           do {
-                               crReturnV;
-                               if (pktin) {
-                                   bombout(("Unexpected data from server"
-                                            " while waiting for agent"
-                                            " response"));
-                                   crStopV;
-                               }
-                           } while (pktin || inlen > 0);
-                           vret = ssh->agent_response;
-                           s->retlen = ssh->agent_response_len;
-                       }
-                       s->ret = vret;
-                       sfree(s->agentreq);
-                       if (s->ret) {
-                           if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
-                               logevent("Sending Pageant's response");
-                               ssh2_add_sigblob(ssh, s->pktout,
-                                                s->pkblob, s->pklen,
-                                                s->ret + 9,
-                                                GET_32BIT(s->ret + 5));
-                               ssh2_pkt_send(ssh, s->pktout);
-                               s->authed = TRUE;
-                               break;
-                           } else {
-                               logevent
-                                   ("Pageant failed to answer challenge");
-                               sfree(s->ret);
+                   }
+                   memcpy(s->q, ssh->v2_session_id,
+                          ssh->v2_session_id_len);
+                   s->q += ssh->v2_session_id_len;
+                   memcpy(s->q, s->pktout->data + 5,
+                          s->pktout->length - 5);
+                   s->q += s->pktout->length - 5;
+                   /* And finally the (zero) flags word. */
+                   PUT_32BIT(s->q, 0);
+                   if (!agent_query(s->agentreq, s->len + 4,
+                                    &vret, &s->retlen,
+                                    ssh_agent_callback, ssh)) {
+                       do {
+                           crReturnV;
+                           if (pktin) {
+                               bombout(("Unexpected data from server"
+                                        " while waiting for agent"
+                                        " response"));
+                               crStopV;
                            }
+                       } while (pktin || inlen > 0);
+                       vret = ssh->agent_response;
+                       s->retlen = ssh->agent_response_len;
+                   }
+                   s->ret = vret;
+                   sfree(s->agentreq);
+                   if (s->ret) {
+                       if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+                           logevent("Sending Pageant's response");
+                           ssh2_add_sigblob(ssh, s->pktout,
+                                            s->pkblob, s->pklen,
+                                            s->ret + 9,
+                                            GET_32BIT(s->ret + 5));
+                           ssh2_pkt_send(ssh, s->pktout);
+                           s->type = AUTH_TYPE_PUBLICKEY;
+                       } else {
+                           /* FIXME: less drastic response */
+                           bombout(("Pageant failed to answer challenge"));
+                           crStopV;
                        }
                    }
-                   if (s->authed)
-                       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;
+               /* Do we have any keys left to try? */
+               if (s->pkblob_in_agent) {
+                   s->done_agent = TRUE;
+                   s->tried_pubkey_config = TRUE;
+               } else {
+                   s->keyi++;
+                   if (s->keyi >= s->nkeys)
+                       s->done_agent = TRUE;
+               }
 
-               s->tried_pubkey_config = TRUE;
+           } else if (s->can_pubkey && s->publickey_blob &&
+                      !s->tried_pubkey_config) {
+
+               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);
-               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 */
                }
+               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.
-                        */
-                       s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-                       ssh2_pkt_adduint32(s->pktout,SSH2_DISCONNECT_BY_APPLICATION);
-                       ssh2_pkt_addstring(s->pktout, "No more passwords available"
-                                          " to try");
-                       ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-                       ssh2_pkt_send_noqueue(ssh, s->pktout);
-                       logevent("Unable to authenticate");
-                       connection_fatal(ssh->frontend,
-                                        "Unable to authenticate");
-                       ssh->close_expected = TRUE;
-                        ssh_closing((Plug)ssh, NULL, 0, 0);
-                       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
@@ -7117,13 +7449,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:
@@ -7133,20 +7470,23 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     * followed by everything so far placed in the
                     * outgoing packet.
                     */
-                   sigdata_len = s->pktout->length - 5 + 4 + 20;
-                    if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                        sigdata_len -= 4;
+                   sigdata_len = s->pktout->length - 5 + 4 +
+                       ssh->v2_session_id_len;
+                   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, 20);
-                        p += 4;
-                    }
-                   memcpy(sigdata+p, ssh->v2_session_id, 20); p += 20;
+                   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,
@@ -7159,8 +7499,196 @@ 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.
+                */
+
+               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 (or, bizarrely but legally, accepts the
+                    * user without actually issuing any prompts).
+                    * 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;
+               }
+
+               /*
+                * Loop while the server continues to send INFO_REQUESTs.
+                */
+               while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+
+                   char *name, *inst, *lang;
+                   int name_len, inst_len, lang_len;
+                   int i;
+
+                   /*
+                    * 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;
+                   }
+                   /* FIXME: ugly to print "Using..." in prompt _every_
+                    * time round. Can this be done more subtly? */
+                   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);
+                   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_with_padding(ssh, s->pktout, 256);
+
+                   /*
+                    * Get the next packet in case it's another
+                    * INFO_REQUEST.
+                    */
+                   crWaitUntilV(pktin);
+
+               }
+
+               /*
+                * We should have SUCCESS or FAILURE now.
+                */
+               s->gotit = TRUE;
+
+           } else if (s->can_passwd) {
+
+               /*
+                * Plain old password authentication.
+                */
+               int ret; /* not live over crReturn */
+               int changereq_first_time; /* not live over crReturn */
+
+               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;
+               }
+               /*
+                * Squirrel away the password. (We may need it later if
+                * asked to change it.)
+                */
+               s->password = dupstr(s->cur_prompt->prompts[0]->result);
+               free_prompts(s->cur_prompt);
+
+               /*
+                * 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.
@@ -7170,67 +7698,205 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                 * people who find out how long their password is!
                 */
                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));
                end_log_omission(ssh, s->pktout);
-               ssh2_pkt_send(ssh, s->pktout);
+               ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
                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! */
+
+               /*
+                * Wait for next packet, in case it's a password change
+                * request.
+                */
+               crWaitUntilV(pktin);
+               changereq_first_time = TRUE;
+
+               while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+
+                   /* 
+                    * We're being asked for a new password
+                    * (perhaps not for the first time).
+                    * Loop until the server accepts it.
+                    */
+
+                   int got_new = FALSE; /* not live over crReturn */
+                   char *prompt;   /* not live over crReturn */
+                   int prompt_len; /* not live over crReturn */
+                   
+                   {
+                       char *msg;
+                       if (changereq_first_time)
+                           msg = "Server requested password change";
+                       else
+                           msg = "Server rejected new password";
+                       logevent(msg);
+                       c_write_str(ssh, msg);
+                       c_write_str(ssh, "\r\n");
+                   }
+
+                   ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+
+                   s->cur_prompt = new_prompts(ssh->frontend);
+                   s->cur_prompt->to_server = TRUE;
+                   s->cur_prompt->name = dupstr("New SSH password");
+                   s->cur_prompt->instruction =
+                       dupprintf("%.*s", prompt_len, prompt);
+                   s->cur_prompt->instr_reqd = TRUE;
+                   /*
+                    * There's no explicit requirement in the protocol
+                    * for the "old" passwords in the original and
+                    * password-change messages to be the same, and
+                    * apparently some Cisco kit supports password change
+                    * by the user entering a blank password originally
+                    * and the real password subsequently, so,
+                    * reluctantly, we prompt for the old password again.
+                    *
+                    * (On the other hand, some servers don't even bother
+                    * to check this field.)
+                    */
+                   add_prompt(s->cur_prompt,
+                              dupstr("Current password (blank for previously entered password): "),
+                              FALSE, SSH_MAX_PASSWORD_LEN);
+                   add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+                              FALSE, SSH_MAX_PASSWORD_LEN);
+                   add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+                              FALSE, SSH_MAX_PASSWORD_LEN);
+
+                   /*
+                    * Loop until the user manages to enter the same
+                    * password twice.
+                    */
+                   while (!got_new) {
+
+                       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.
+                            */
+                           /* burn the evidence */
+                           free_prompts(s->cur_prompt);
+                           memset(s->password, 0, strlen(s->password));
+                           sfree(s->password);
+                           ssh_disconnect(ssh, NULL, "Unable to authenticate",
+                                          SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+                                          TRUE);
+                           crStopV;
+                       }
+
+                       /*
+                        * If the user specified a new original password
+                        * (IYSWIM), overwrite any previously specified
+                        * one.
+                        * (A side effect is that the user doesn't have to
+                        * re-enter it if they louse up the new password.)
+                        */
+                       if (s->cur_prompt->prompts[0]->result[0]) {
+                           memset(s->password, 0, strlen(s->password));
+                               /* burn the evidence */
+                           sfree(s->password);
+                           s->password =
+                               dupstr(s->cur_prompt->prompts[0]->result);
+                       }
+
+                       /*
+                        * Check the two new passwords match.
+                        */
+                       got_new = (strcmp(s->cur_prompt->prompts[1]->result,
+                                         s->cur_prompt->prompts[2]->result)
+                                  == 0);
+                       if (!got_new)
+                           /* They don't. Silly user. */
+                           c_write_str(ssh, "Passwords do not match\r\n");
+
+                   }
+
+                   /*
+                    * Send the new password (along with the old one).
+                    * (see above for padding rationale)
+                    */
+                   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, "password");
+                   ssh2_pkt_addbool(s->pktout, TRUE);
                    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[1]->result);
+                   free_prompts(s->cur_prompt);
                    end_log_omission(ssh, s->pktout);
-                   s->curr_prompt++;
-               }
-               if (s->curr_prompt >= s->num_prompts) {
-                   ssh2_pkt_send(ssh, s->pktout);
-               } else {
+                   ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
+                   logevent("Sent new password");
+                   
                    /*
-                    * 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?
+                    * Now see what the server has to say about it.
+                    * (If it's CHANGEREQ again, it's not happy with the
+                    * new password.)
                     */
-                   s->gotit = TRUE;
+                   crWaitUntilV(pktin);
+                   changereq_first_time = FALSE;
+
                }
-               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+               /*
+                * We need to reexamine the current pktin at the top
+                * of the loop. Either:
+                *  - we weren't asked to change password at all, in
+                *    which case it's a SUCCESS or FAILURE with the
+                *    usual meaning
+                *  - we sent a new password, and the server was
+                *    either OK with it (SUCCESS or FAILURE w/partial
+                *    success) or unhappy with the _old_ password
+                *    (FAILURE w/o partial success)
+                * In any of these cases, we go back to the top of
+                * the loop and start again.
+                */
+               s->gotit = TRUE;
+
+               /*
+                * We don't need the old password any more, in any
+                * case. Burn the evidence.
+                */
+               memset(s->password, 0, strlen(s->password));
+               sfree(s->password);
+
            } else {
-               c_write_str(ssh, "No supported authentication methods"
-                           " left to try!\r\n");
-               logevent("No supported authentications offered."
-                        " Disconnecting");
-               s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-               ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION);
-               ssh2_pkt_addstring(s->pktout, "No supported authentication"
-                                  " methods available");
-               ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-               ssh2_pkt_send_noqueue(ssh, s->pktout);
-               ssh->close_expected = TRUE;
-                ssh_closing((Plug)ssh, NULL, 0, 0);
+
+               ssh_disconnect(ssh, NULL,
+                              "No supported authentication methods available",
+                              SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+                              FALSE);
                crStopV;
+
            }
+
        }
-    } while (!s->we_are_in);
+    }
+    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);
+    }
+    if (s->agent_response)
+       sfree(s->agent_response);
 
     /*
-     * Now we're authenticated for the connection protocol. The
-     * connection protocol will automatically have started at this
-     * point; there's no need to send SERVICE_REQUEST.
+     * Now the connection protocol has started, one way or another.
      */
 
     ssh->channels = newtree234(ssh_channelcmp);
@@ -7247,7 +7913,58 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Create the main session channel.
      */
-    if (!ssh->cfg.ssh_no_shell) {
+    if (ssh->cfg.ssh_no_shell) {
+       ssh->mainchan = NULL;
+    } else if (*ssh->cfg.ssh_nc_host) {
+       /*
+        * Just start a direct-tcpip channel and use it as the main
+        * channel.
+        */
+       ssh->mainchan = snew(struct ssh_channel);
+       ssh->mainchan->ssh = ssh;
+       ssh->mainchan->localid = alloc_channel_id(ssh);
+       logeventf(ssh,
+                 "Opening direct-tcpip channel to %s:%d in place of session",
+                 ssh->cfg.ssh_nc_host, ssh->cfg.ssh_nc_port);
+       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+       ssh2_pkt_addstring(s->pktout, "direct-tcpip");
+       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
+       ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
+       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
+       ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT);      /* our max pkt size */
+       ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host);
+       ssh2_pkt_adduint32(s->pktout, ssh->cfg.ssh_nc_port);
+       /*
+        * There's nothing meaningful to put in the originator
+        * fields, but some servers insist on syntactically correct
+        * information.
+        */
+       ssh2_pkt_addstring(s->pktout, "0.0.0.0");
+       ssh2_pkt_adduint32(s->pktout, 0);
+       ssh2_pkt_send(ssh, s->pktout);
+
+       crWaitUntilV(pktin);
+       if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+           bombout(("Server refused to open a direct-tcpip channel"));
+           crStopV;
+           /* FIXME: error data comes back in FAILURE packet */
+       }
+       if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
+           bombout(("Server's channel confirmation cited wrong channel"));
+           crStopV;
+       }
+       ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
+       ssh->mainchan->halfopen = FALSE;
+       ssh->mainchan->type = CHAN_MAINSESSION;
+       ssh->mainchan->closes = 0;
+       ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
+       ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
+       bufchain_init(&ssh->mainchan->v.v2.outbuffer);
+       add234(ssh->channels, ssh->mainchan);
+       update_specials_menu(ssh->frontend);
+       logevent("Opened direct-tcpip channel");
+       ssh->ncmode = TRUE;
+    } else {
        ssh->mainchan = snew(struct ssh_channel);
        ssh->mainchan->ssh = ssh;
        ssh->mainchan->localid = alloc_channel_id(ssh);
@@ -7278,8 +7995,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        add234(ssh->channels, ssh->mainchan);
        update_specials_menu(ssh->frontend);
        logevent("Opened channel for session");
-    } else
-       ssh->mainchan = NULL;
+       ssh->ncmode = FALSE;
+    }
 
     /*
      * Now we have a channel, make dispatch table entries for
@@ -7302,7 +8019,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Potentially enable X11 forwarding.
      */
-    if (ssh->mainchan && ssh->cfg.x11_forward) {
+    if (ssh->mainchan && !ssh->ncmode && ssh->cfg.x11_forward) {
        char proto[20], data[64];
        logevent("Requesting X11 forwarding");
        ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
@@ -7314,6 +8031,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
        ssh2_pkt_addbool(s->pktout, 0);        /* many connections */
        ssh2_pkt_addstring(s->pktout, proto);
+       /*
+        * Note that while we blank the X authentication data here, we don't
+        * take any special action to blank the start of an X11 channel,
+        * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+        * without having session blanking enabled is likely to leak your
+        * cookie into the log.
+        */
        dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
        ssh2_pkt_addstring(s->pktout, data);
        end_log_omission(ssh, s->pktout);
@@ -7343,7 +8067,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Potentially enable agent forwarding.
      */
-    if (ssh->mainchan && ssh->cfg.agentfwd && agent_exists()) {
+    if (ssh->mainchan && !ssh->ncmode && ssh->cfg.agentfwd && agent_exists()) {
        logevent("Requesting OpenSSH-style agent forwarding");
        s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
        ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
@@ -7369,7 +8093,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Now allocate a pty for the session.
      */
-    if (ssh->mainchan && !ssh->cfg.nopty) {
+    if (ssh->mainchan && !ssh->ncmode && !ssh->cfg.nopty) {
        /* Unpick the terminal-speed string. */
        /* XXX perhaps we should allow no speeds to be sent. */
         ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
@@ -7419,7 +8143,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      * Simplest thing here is to send all the requests at once, and
      * then wait for a whole bunch of successes or failures.
      */
-    if (ssh->mainchan && *ssh->cfg.environmt) {
+    if (ssh->mainchan && !ssh->ncmode && *ssh->cfg.environmt) {
        char *e = ssh->cfg.environmt;
        char *var, *varend, *val;
 
@@ -7484,7 +8208,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      * this twice if the config data has provided a second choice
      * of command.
      */
-    if (ssh->mainchan) while (1) {
+    if (ssh->mainchan && !ssh->ncmode) while (1) {
        int subsys;
        char *cmd;
 
@@ -7789,6 +8513,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->hostkey = NULL;
     ssh->exitcode = -1;
     ssh->close_expected = FALSE;
+    ssh->clean_exit = FALSE;
     ssh->state = SSH_STATE_PREPACKET;
     ssh->size_needed = FALSE;
     ssh->eof_needed = FALSE;
@@ -7814,6 +8539,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->do_ssh1_login_state = NULL;
     ssh->do_ssh2_transport_state = NULL;
     ssh->do_ssh2_authconn_state = NULL;
+    ssh->v_c = NULL;
+    ssh->v_s = NULL;
     ssh->mainchan = NULL;
     ssh->throttled_all = 0;
     ssh->v1_stdout_throttling = 0;
@@ -7941,6 +8668,8 @@ static void ssh_free(void *handle)
     sfree(ssh->do_ssh1_login_state);
     sfree(ssh->do_ssh2_transport_state);
     sfree(ssh->do_ssh2_authconn_state);
+    sfree(ssh->v_c);
+    sfree(ssh->v_s);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
@@ -7966,7 +8695,8 @@ static void ssh_reconfig(void *handle, Config *cfg)
     unsigned long old_max_data_size;
 
     pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
-    ssh_setup_portfwd(ssh, cfg);
+    if (ssh->portfwds)
+       ssh_setup_portfwd(ssh, cfg);
 
     if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time &&
        cfg->ssh_rekey_time != 0) {
@@ -8013,7 +8743,7 @@ static void ssh_reconfig(void *handle, Config *cfg)
 }
 
 /*
- * Called to send data down the Telnet connection.
+ * Called to send data down the SSH connection.
  */
 static int ssh_send(void *handle, char *buf, int len)
 {
@@ -8170,7 +8900,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 }
 
 /*
- * Send Telnet special codes. TS_EOF is useful for `plink', so you
+ * Send special codes. TS_EOF is useful for `plink', so you
  * can send an EOF and collect resulting output (e.g. `plink
  * hostname sort').
  */
@@ -8293,8 +9023,7 @@ static void ssh_unthrottle(void *handle, int bufsize)
            ssh1_throttle(ssh, -1);
        }
     } else {
-       if (ssh->mainchan && ssh->mainchan->closes == 0)
-           ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize);
+       ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize);
     }
 }
 
@@ -8327,17 +9056,20 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
         * too much hassle to keep track, and partly I'm not
         * convinced the server should be told details like that
         * about my local network configuration.
+        * The "originator IP address" is syntactically a numeric
+        * IP address, and some servers (e.g., Tectia) get upset
+        * if it doesn't match this syntax.
         */
-       ssh2_pkt_addstring(pktout, "client-side-connection");
+       ssh2_pkt_addstring(pktout, "0.0.0.0");
        ssh2_pkt_adduint32(pktout, 0);
        ssh2_pkt_send(ssh, pktout);
     }
 }
 
-static Socket ssh_socket(void *handle)
+static int ssh_connected(void *handle)
 {
     Ssh ssh = (Ssh) handle;
-    return ssh->s;
+    return ssh->s != NULL;
 }
 
 static int ssh_sendok(void *handle)
@@ -8374,7 +9106,7 @@ static int ssh_return_exitcode(void *handle)
     if (ssh->s != NULL)
         return -1;
     else
-        return (ssh->exitcode >= 0 ? ssh->exitcode : 0);
+        return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
 }
 
 /*
@@ -8407,7 +9139,7 @@ Backend ssh_backend = {
     ssh_size,
     ssh_special,
     ssh_get_specials,
-    ssh_socket,
+    ssh_connected,
     ssh_return_exitcode,
     ssh_sendok,
     ssh_ldisc,