Major destabilisation, phase 1. In this phase I've moved (I think)
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index baa811f..31d4cc4 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -5,6 +5,7 @@
 #include <assert.h>
 
 #include "putty.h"
+#include "terminal.h"
 #include "tree234.h"
 #include "ssh.h"
 
 #define TRUE 1
 #endif
 
-/* uncomment this for packet level debugging */
-/* #define DUMP_PACKETS */
-
 #define logevent(s) { logevent(s); \
                       if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
                       { fprintf(stderr, "%s\n", s); fflush(stderr); } }
 
+/* logevent, only printf-formatted. */
+void logeventf(char *fmt, ...)
+{
+    va_list ap;
+    char stuff[200];
+
+    va_start(ap, fmt);
+    vsprintf(stuff, fmt, ap);
+    va_end(ap);
+    logevent(stuff);
+}
+
 #define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \
                           (s ? sk_close(s), s = NULL : 0), \
-                          connection_fatal msg )
+                          logeventf msg, connection_fatal msg )
 
 #define SSH1_MSG_DISCONNECT                       1    /* 0x1 */
 #define SSH1_SMSG_PUBLIC_KEY                      2    /* 0x2 */
 #define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
 #define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
 
+/*
+ * Packet type contexts, so that ssh2_pkt_type can correctly decode
+ * the ambiguous type numbers back into the correct type strings.
+ */
+#define SSH2_PKTCTX_DHGROUP1         0x0001
+#define SSH2_PKTCTX_DHGEX            0x0002
+#define SSH2_PKTCTX_PUBLICKEY        0x0010
+#define SSH2_PKTCTX_PASSWORD         0x0020
+#define SSH2_PKTCTX_KBDINTER         0x0040
+#define SSH2_PKTCTX_AUTH_MASK        0x00F0
+
 #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1  /* 0x1 */
 #define SSH2_DISCONNECT_PROTOCOL_ERROR            2    /* 0x2 */
 #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3    /* 0x3 */
@@ -160,7 +181,102 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_CHOKES_ON_SSH1_IGNORE                 1
 #define BUG_SSH2_HMAC                             2
 #define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
+#define BUG_CHOKES_ON_RSA                        8
+#define BUG_SSH2_RSA_PADDING                    16
+#define BUG_SSH2_DERIVEKEY                       32
+#define BUG_SSH2_DH_GEX                          64
 
+static int ssh_pkt_ctx = 0;
+
+#define translate(x) if (type == x) return #x
+#define translatec(x,ctx) if (type == x && (ssh_pkt_ctx & ctx)) return #x
+char *ssh1_pkt_type(int type)
+{
+    translate(SSH1_MSG_DISCONNECT);
+    translate(SSH1_SMSG_PUBLIC_KEY);
+    translate(SSH1_CMSG_SESSION_KEY);
+    translate(SSH1_CMSG_USER);
+    translate(SSH1_CMSG_AUTH_RSA);
+    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+    translate(SSH1_CMSG_AUTH_PASSWORD);
+    translate(SSH1_CMSG_REQUEST_PTY);
+    translate(SSH1_CMSG_WINDOW_SIZE);
+    translate(SSH1_CMSG_EXEC_SHELL);
+    translate(SSH1_CMSG_EXEC_CMD);
+    translate(SSH1_SMSG_SUCCESS);
+    translate(SSH1_SMSG_FAILURE);
+    translate(SSH1_CMSG_STDIN_DATA);
+    translate(SSH1_SMSG_STDOUT_DATA);
+    translate(SSH1_SMSG_STDERR_DATA);
+    translate(SSH1_CMSG_EOF);
+    translate(SSH1_SMSG_EXIT_STATUS);
+    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH1_MSG_CHANNEL_DATA);
+    translate(SSH1_MSG_CHANNEL_CLOSE);
+    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+    translate(SSH1_SMSG_X11_OPEN);
+    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+    translate(SSH1_MSG_PORT_OPEN);
+    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    translate(SSH1_SMSG_AGENT_OPEN);
+    translate(SSH1_MSG_IGNORE);
+    translate(SSH1_CMSG_EXIT_CONFIRMATION);
+    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+    translate(SSH1_MSG_DEBUG);
+    translate(SSH1_CMSG_REQUEST_COMPRESSION);
+    translate(SSH1_CMSG_AUTH_TIS);
+    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+    translate(SSH1_CMSG_AUTH_CCARD);
+    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+    return "unknown";
+}
+char *ssh2_pkt_type(int type)
+{
+    translate(SSH2_MSG_DISCONNECT);
+    translate(SSH2_MSG_IGNORE);
+    translate(SSH2_MSG_UNIMPLEMENTED);
+    translate(SSH2_MSG_DEBUG);
+    translate(SSH2_MSG_SERVICE_REQUEST);
+    translate(SSH2_MSG_SERVICE_ACCEPT);
+    translate(SSH2_MSG_KEXINIT);
+    translate(SSH2_MSG_NEWKEYS);
+    translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+    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);
+    translate(SSH2_MSG_USERAUTH_REQUEST);
+    translate(SSH2_MSG_USERAUTH_FAILURE);
+    translate(SSH2_MSG_USERAUTH_SUCCESS);
+    translate(SSH2_MSG_USERAUTH_BANNER);
+    translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+    translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+    translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+    translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+    translate(SSH2_MSG_GLOBAL_REQUEST);
+    translate(SSH2_MSG_REQUEST_SUCCESS);
+    translate(SSH2_MSG_REQUEST_FAILURE);
+    translate(SSH2_MSG_CHANNEL_OPEN);
+    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+    translate(SSH2_MSG_CHANNEL_DATA);
+    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+    translate(SSH2_MSG_CHANNEL_EOF);
+    translate(SSH2_MSG_CHANNEL_CLOSE);
+    translate(SSH2_MSG_CHANNEL_REQUEST);
+    translate(SSH2_MSG_CHANNEL_SUCCESS);
+    translate(SSH2_MSG_CHANNEL_FAILURE);
+    return "unknown";
+}
+#undef translate
+#undef translatec
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -210,6 +326,18 @@ extern void pfd_confirm(Socket s);
 extern void pfd_unthrottle(Socket s);
 extern void pfd_override_throttle(Socket s, int enable);
 
+static void ssh2_pkt_init(int pkt_type);
+static void ssh2_pkt_addbool(unsigned char value);
+static void ssh2_pkt_adduint32(unsigned long value);
+static void ssh2_pkt_addstring_start(void);
+static void ssh2_pkt_addstring_str(char *data);
+static void ssh2_pkt_addstring_data(char *data, int len);
+static void ssh2_pkt_addstring(char *data);
+static char *ssh2_mpint_fmt(Bignum b, int *len);
+static void ssh2_pkt_addmp(Bignum b);
+static int ssh2_pkt_construct(void);
+static void ssh2_pkt_send(void);
+
 /*
  * Buffer management constants. There are several of these for
  * various different purposes:
@@ -235,16 +363,6 @@ extern void pfd_override_throttle(Socket s, int enable);
 #define SSH_MAX_BACKLOG 32768
 #define OUR_V2_WINSIZE 16384
 
-/*
- * Ciphers for SSH2.
- */
-const static struct ssh2_ciphers *ciphers[] = {
-    &ssh2_aes,
-    &ssh2_blowfish,
-    &ssh2_3des,
-    &ssh2_des,
-};
-
 const static struct ssh_kex *kex_algs[] = {
     &ssh_diffiehellman_gex,
     &ssh_diffiehellman
@@ -310,6 +428,16 @@ enum {                                    /* channel types */
 struct ssh_channel {
     unsigned remoteid, localid;
     int type;
+    /*
+     * In SSH1, this value contains four bits:
+     * 
+     *   1   We have sent SSH1_MSG_CHANNEL_CLOSE.
+     *   2   We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+     *   4   We have received SSH1_MSG_CHANNEL_CLOSE.
+     *   8   We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+     * 
+     * A channel is completely finished with when all four bits are set.
+     */
     int closes;
     union {
        struct ssh1_data_channel {
@@ -400,16 +528,17 @@ static const struct ssh_compress *sccomp = NULL;
 static const struct ssh_kex *kex = NULL;
 static const struct ssh_signkey *hostkey = NULL;
 static unsigned char ssh2_session_id[20];
-int (*ssh_get_line) (const char *prompt, char *str, int maxlen,
-                    int is_pw) = NULL;
 
 static char *savedhost;
 static int savedport;
 static int ssh_send_ok;
 static int ssh_echoing, ssh_editing;
 
+static void *frontend;
+
 static tree234 *ssh_channels;         /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
+static int ssh_exitcode = -1;
 
 static tree234 *ssh_rportfwds;
 
@@ -562,7 +691,7 @@ static void c_write(char *buf, int len)
                fputc(buf[i], stderr);
        return;
     }
-    from_backend(1, buf, len);
+    from_backend(frontend, 1, buf, len);
 }
 
 static void c_write_untrusted(char *buf, int len)
@@ -634,12 +763,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        st->to_read -= st->chunk;
     }
 
+    if (cipher && detect_attack(pktin.data, st->biglen, NULL)) {
+        bombout(("Network attack (CRC compensation) detected!"));
+        crReturn(0);
+    }
+
     if (cipher)
        cipher->decrypt(pktin.data, st->biglen);
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->biglen);
-#endif
 
     st->realcrc = crc32(pktin.data, st->biglen - 4);
     st->gotcrc = GET_32BIT(pktin.data + st->biglen - 4);
@@ -653,10 +783,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
     if (ssh1_compressing) {
        unsigned char *decompblk;
        int decomplen;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload pre-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
        zlib_decompress_block(pktin.body - 1, pktin.length + 1,
                              &decompblk, &decomplen);
 
@@ -671,35 +797,34 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        memcpy(pktin.body - 1, decompblk, decomplen);
        sfree(decompblk);
        pktin.length = decomplen - 1;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload post-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
     }
 
+    pktin.type = pktin.body[-1];
+
+    log_packet(PKT_INCOMING, pktin.type, ssh1_pkt_type(pktin.type),
+              pktin.body, pktin.length);
+
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
        pktin.type == SSH1_SMSG_STDERR_DATA ||
        pktin.type == SSH1_MSG_DEBUG ||
        pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
        pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
-       long strlen = GET_32BIT(pktin.body);
-       if (strlen + 4 != pktin.length) {
+       long stringlen = GET_32BIT(pktin.body);
+       if (stringlen + 4 != pktin.length) {
            bombout(("Received data packet with bogus string length"));
            crReturn(0);
        }
     }
 
-    pktin.type = pktin.body[-1];
-
     if (pktin.type == SSH1_MSG_DEBUG) {
        /* log debug message */
-       char buf[80];
-       int strlen = GET_32BIT(pktin.body);
-       strcpy(buf, "Remote: ");
-       if (strlen > 70)
-           strlen = 70;
-       memcpy(buf + 8, pktin.body + 4, strlen);
-       buf[8 + strlen] = '\0';
+       char buf[512];
+       int stringlen = GET_32BIT(pktin.body);
+       strcpy(buf, "Remote debug message: ");
+       if (stringlen > 480)
+           stringlen = 480;
+       memcpy(buf + 8, pktin.body + 4, stringlen);
+       buf[8 + stringlen] = '\0';
        logevent(buf);
        goto next_packet;
     } else if (pktin.type == SSH1_MSG_IGNORE) {
@@ -718,7 +843,7 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
            msglen = sizeof(buf) - nowlen - 1;
        memcpy(buf + nowlen, pktin.body + 4, msglen);
        buf[nowlen + msglen] = '\0';
-       logevent(buf);
+       /* logevent(buf); (this is now done within the bombout macro) */
        bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen));
        crReturn(0);
     }
@@ -775,6 +900,15 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
     st->pad = pktin.data[4];
 
     /*
+     * _Completely_ silly lengths should be stomped on before they
+     * do us any more damage.
+     */
+    if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
+       bombout(("Incoming packet was garbled on decryption"));
+       crReturn(0);
+    }
+
+    /*
      * This enables us to deduce the payload length.
      */
     st->payload = st->len - st->pad - 1;
@@ -817,11 +951,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
        sccipher->decrypt(pktin.data + st->cipherblk,
                          st->packetlen - st->cipherblk);
 
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->packetlen);
-#endif
-
     /*
      * Check the MAC.
      */
@@ -854,11 +983,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
            }
            pktin.length = 5 + newlen;
            memcpy(pktin.data + 5, newpayload, newlen);
-#ifdef DUMP_PACKETS
-           debug(("Post-decompression payload:\n"));
-           dmemdump(pktin.data + 5, newlen);
-#endif
-
            sfree(newpayload);
        }
     }
@@ -866,36 +990,108 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
     pktin.savedpos = 6;
     pktin.type = pktin.data[5];
 
-    if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
-       goto next_packet;              /* FIXME: print DEBUG message */
-
-    if (pktin.type == SSH2_MSG_DISCONNECT) {
-       /* log reason code in disconnect message */
-       char buf[256];
-       int reason = GET_32BIT(pktin.data + 6);
-       unsigned msglen = GET_32BIT(pktin.data + 10);
-       unsigned nowlen;
-       if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
-           sprintf(buf, "Received disconnect message (%s)",
-                   ssh2_disconnect_reasons[reason]);
-       } else {
-           sprintf(buf, "Received disconnect message (unknown type %d)",
-                   reason);
+    log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type),
+              pktin.data+6, pktin.length-6);
+
+    switch (pktin.type) {
+        /*
+         * These packets we must handle instantly.
+         */
+      case SSH2_MSG_DISCONNECT:
+        {
+            /* log reason code in disconnect message */
+            char buf[256];
+            int reason = GET_32BIT(pktin.data + 6);
+            unsigned msglen = GET_32BIT(pktin.data + 10);
+            unsigned nowlen;
+            if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+                sprintf(buf, "Received disconnect message (%s)",
+                        ssh2_disconnect_reasons[reason]);
+            } else {
+                sprintf(buf, "Received disconnect message (unknown type %d)",
+                        reason);
+            }
+            logevent(buf);
+            strcpy(buf, "Disconnection message text: ");
+            nowlen = strlen(buf);
+            if (msglen > sizeof(buf) - nowlen - 1)
+                msglen = sizeof(buf) - nowlen - 1;
+            memcpy(buf + nowlen, pktin.data + 14, msglen);
+            buf[nowlen + msglen] = '\0';
+            logevent(buf);
+            bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
+                     reason,
+                     (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+                     ssh2_disconnect_reasons[reason] : "unknown",
+                     buf+nowlen));
+            crReturn(0);
+        }
+        break;
+      case SSH2_MSG_IGNORE:
+       goto next_packet;
+      case SSH2_MSG_DEBUG:
+       {
+           /* log the debug message */
+           char buf[512];
+           /* int display = pktin.body[6]; */
+           int stringlen = GET_32BIT(pktin.data+7);
+           int prefix;
+           strcpy(buf, "Remote debug message: ");
+           prefix = strlen(buf);
+           if (stringlen > sizeof(buf)-prefix-1)
+               stringlen = sizeof(buf)-prefix-1;
+           memcpy(buf + prefix, pktin.data + 11, stringlen);
+           buf[prefix + stringlen] = '\0';
+           logevent(buf);
        }
-       logevent(buf);
-       strcpy(buf, "Disconnection message text: ");
-       nowlen = strlen(buf);
-       if (msglen > sizeof(buf) - nowlen - 1)
-           msglen = sizeof(buf) - nowlen - 1;
-       memcpy(buf + nowlen, pktin.data + 14, msglen);
-       buf[nowlen + msglen] = '\0';
-       logevent(buf);
-       bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
-                reason,
-                (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
-                ssh2_disconnect_reasons[reason] : "unknown",
-                buf+nowlen));
-       crReturn(0);
+        goto next_packet;              /* FIXME: print the debug message */
+
+        /*
+         * These packets we need do nothing about here.
+         */
+      case SSH2_MSG_UNIMPLEMENTED:
+      case SSH2_MSG_SERVICE_REQUEST:
+      case SSH2_MSG_SERVICE_ACCEPT:
+      case SSH2_MSG_KEXINIT:
+      case SSH2_MSG_NEWKEYS:
+      case SSH2_MSG_KEXDH_INIT:
+      case SSH2_MSG_KEXDH_REPLY:
+      /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */
+      /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */
+      case SSH2_MSG_KEX_DH_GEX_INIT:
+      case SSH2_MSG_KEX_DH_GEX_REPLY:
+      case SSH2_MSG_USERAUTH_REQUEST:
+      case SSH2_MSG_USERAUTH_FAILURE:
+      case SSH2_MSG_USERAUTH_SUCCESS:
+      case SSH2_MSG_USERAUTH_BANNER:
+      case SSH2_MSG_USERAUTH_PK_OK:
+      /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */
+      /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */
+      case SSH2_MSG_USERAUTH_INFO_RESPONSE:
+      case SSH2_MSG_GLOBAL_REQUEST:
+      case SSH2_MSG_REQUEST_SUCCESS:
+      case SSH2_MSG_REQUEST_FAILURE:
+      case SSH2_MSG_CHANNEL_OPEN:
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+      case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+      case SSH2_MSG_CHANNEL_DATA:
+      case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+      case SSH2_MSG_CHANNEL_EOF:
+      case SSH2_MSG_CHANNEL_CLOSE:
+      case SSH2_MSG_CHANNEL_REQUEST:
+      case SSH2_MSG_CHANNEL_SUCCESS:
+      case SSH2_MSG_CHANNEL_FAILURE:
+        break;
+
+        /*
+         * For anything else we send SSH2_MSG_UNIMPLEMENTED.
+         */
+      default:
+       ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
+       ssh2_pkt_adduint32(st->incoming_sequence - 1);
+       ssh2_pkt_send();
+        break;
     }
 
     crFinish(0);
@@ -940,13 +1136,12 @@ static int s_wrpkt_prepare(void)
 
     pktout.body[-1] = pktout.type;
 
+    log_packet(PKT_OUTGOING, pktout.type, ssh1_pkt_type(pktout.type),
+              pktout.body, pktout.length);
+
     if (ssh1_compressing) {
        unsigned char *compblk;
        int complen;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload pre-compression:\n"));
-       dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
        zlib_compress_block(pktout.body - 1, pktout.length + 1,
                            &compblk, &complen);
        ssh1_pktout_size(complen - 1);
@@ -964,10 +1159,6 @@ static int s_wrpkt_prepare(void)
     PUT_32BIT(pktout.data + biglen, crc);
     PUT_32BIT(pktout.data, len);
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", biglen + 4));
-    dmemdump(pktout.data, biglen + 4);
-#endif
     if (cipher)
        cipher->encrypt(pktout.data + 4, biglen);
 
@@ -1231,18 +1422,15 @@ static int ssh2_pkt_construct(void)
     int cipherblk, maclen, padding, i;
     static unsigned long outgoing_sequence = 0;
 
+    log_packet(PKT_OUTGOING, pktout.data[5], ssh2_pkt_type(pktout.data[5]),
+              pktout.data + 6, pktout.length - 6);
+
     /*
      * Compress packet payload.
      */
     {
        unsigned char *newpayload;
        int newlen;
-#ifdef DUMP_PACKETS
-       if (cscomp && cscomp != &ssh_comp_none) {
-           debug(("Pre-compression payload:\n"));
-           dmemdump(pktout.data + 5, pktout.length - 5);
-       }
-#endif
        if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
                                       &newpayload, &newlen)) {
            pktout.length = 5;
@@ -1271,11 +1459,6 @@ static int ssh2_pkt_construct(void)
                        outgoing_sequence);
     outgoing_sequence++;              /* whether or not we MACed */
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", pktout.length + padding));
-    dmemdump(pktout.data, pktout.length + padding);
-#endif
-
     if (cscipher)
        cscipher->encrypt(pktout.data, pktout.length + padding);
 
@@ -1380,6 +1563,7 @@ static int ssh2_pkt_getbool(void)
 static void ssh2_pkt_getstring(char **p, int *length)
 {
     *p = NULL;
+    *length = 0;
     if (pktin.length - pktin.savedpos < 4)
        return;
     *length = GET_32BIT(pktin.data + pktin.savedpos);
@@ -1407,6 +1591,75 @@ static Bignum ssh2_pkt_getmp(void)
 }
 
 /*
+ * Helper function to add an SSH2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(void *pkblob_v, int pkblob_len,
+                            void *sigblob_v, int sigblob_len)
+{
+    unsigned char *pkblob = (unsigned char *)pkblob_v;
+    unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+    /* dmemdump(pkblob, pkblob_len); */
+    /* dmemdump(sigblob, sigblob_len); */
+
+    /*
+     * See if this is in fact an ssh-rsa signature and a buggy
+     * server; otherwise we can just do this the easy way.
+     */
+    if ((ssh_remote_bugs & BUG_SSH2_RSA_PADDING) &&
+       (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
+       int pos, len, siglen;
+
+       /*
+        * Find the byte length of the modulus.
+        */
+
+       pos = 4+7;                     /* skip over "ssh-rsa" */
+       pos += 4 + GET_32BIT(pkblob+pos);   /* skip over exponent */
+       len = GET_32BIT(pkblob+pos);   /* find length of modulus */
+       pos += 4;                      /* find modulus itself */
+       while (len > 0 && pkblob[pos] == 0)
+           len--, pos++;
+       /* debug(("modulus length is %d\n", len)); */
+
+       /*
+        * Now find the signature integer.
+        */
+       pos = 4+7;                     /* skip over "ssh-rsa" */
+       siglen = GET_32BIT(sigblob+pos);
+       /* debug(("signature length is %d\n", siglen)); */
+
+       if (len != siglen) {
+           unsigned char newlen[4];
+           ssh2_pkt_addstring_start();
+           ssh2_pkt_addstring_data(sigblob, pos);
+           /* dmemdump(sigblob, pos); */
+           pos += 4;                  /* point to start of actual sig */
+           PUT_32BIT(newlen, len);
+           ssh2_pkt_addstring_data(newlen, 4);
+           /* dmemdump(newlen, 4); */
+           newlen[0] = 0;
+           while (len-- > siglen) {
+               ssh2_pkt_addstring_data(newlen, 1);
+               /* dmemdump(newlen, 1); */
+           }
+           ssh2_pkt_addstring_data(sigblob+pos, siglen);
+           /* dmemdump(sigblob+pos, siglen); */
+           return;
+       }
+
+       /* Otherwise fall through and do it the easy way. */
+    }
+
+    ssh2_pkt_addstring_start();
+    ssh2_pkt_addstring_data(sigblob, sigblob_len);
+}
+
+/*
  * Examine the remote side's version string and compare it against
  * a list of known buggy implementations.
  */
@@ -1421,9 +1674,11 @@ static void ssh_detect_bugs(char *vstring)
 
     ssh_remote_bugs = 0;
 
-    if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
-       !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
-       !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) {
+    if (cfg.sshbug_ignore1 == BUG_ON ||
+       (cfg.sshbug_ignore1 == BUG_AUTO &&
+        (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+         !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+         !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")))) {
        /*
         * These versions don't support SSH1_MSG_IGNORE, so we have
         * to use a different defence against password length
@@ -1433,7 +1688,9 @@ static void ssh_detect_bugs(char *vstring)
        logevent("We believe remote version has SSH1 ignore bug");
     }
 
-    if (!strcmp(imp, "Cisco-1.25")) {
+    if (cfg.sshbug_plainpw1 == BUG_ON ||
+       (cfg.sshbug_plainpw1 == BUG_AUTO &&
+        (!strcmp(imp, "Cisco-1.25")))) {
        /*
         * These versions need a plain password sent; they can't
         * handle having a null and a random length of data after
@@ -1443,15 +1700,60 @@ static void ssh_detect_bugs(char *vstring)
        logevent("We believe remote version needs a plain SSH1 password");
     }
 
-    if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
-       !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
-       !strncmp(imp, "2.1 ", 4)) {
+    if (cfg.sshbug_rsa1 == BUG_ON ||
+       (cfg.sshbug_rsa1 == BUG_AUTO &&
+        (!strcmp(imp, "Cisco-1.25")))) {
+       /*
+        * These versions apparently have no clue whatever about
+        * RSA authentication and will panic and die if they see
+        * an AUTH_RSA message.
+        */
+       ssh_remote_bugs |= BUG_CHOKES_ON_RSA;
+       logevent("We believe remote version can't handle RSA authentication");
+    }
+
+    if (cfg.sshbug_hmac2 == BUG_ON ||
+       (cfg.sshbug_hmac2 == BUG_AUTO &&
+        (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
+         !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
+         !strncmp(imp, "2.1 ", 4)))) {
        /*
         * These versions have the HMAC bug.
         */
        ssh_remote_bugs |= BUG_SSH2_HMAC;
        logevent("We believe remote version has SSH2 HMAC bug");
     }
+
+    if (cfg.sshbug_derivekey2 == BUG_ON ||
+       (cfg.sshbug_derivekey2 == BUG_AUTO &&
+        (!strncmp(imp, "2.0.", 4)))) {
+       /*
+        * These versions have the key-derivation bug (failing to
+        * include the literal shared secret in the hashes that
+        * generate the keys).
+        */
+       ssh_remote_bugs |= BUG_SSH2_DERIVEKEY;
+       logevent("We believe remote version has SSH2 key-derivation bug");
+    }
+
+    if (cfg.sshbug_rsapad2 == BUG_ON ||
+       (cfg.sshbug_rsapad2 == BUG_AUTO &&
+        ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') ||
+         (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')))){
+       /*
+        * These versions have the SSH2 RSA padding bug.
+        */
+       ssh_remote_bugs |= BUG_SSH2_RSA_PADDING;
+       logevent("We believe remote version has SSH2 RSA padding bug");
+    }
+
+    if (cfg.sshbug_dhgex2 == BUG_ON) {
+       /*
+        * These versions have the SSH2 DH GEX bug.
+        */
+       ssh_remote_bugs |= BUG_SSH2_DH_GEX;
+       logevent("We believe remote version has SSH2 DH group exchange bug");
+    }
 }
 
 static int do_ssh_init(unsigned char c)
@@ -1462,6 +1764,7 @@ static int do_ssh_init(unsigned char c)
     static int vstrsize;
     static char *vlog;
     static int i;
+    static int proto1, proto2;
 
     crBegin;
 
@@ -1518,12 +1821,26 @@ static int do_ssh_init(unsigned char c)
     sfree(vlog);
 
     /*
-     * Server version "1.99" means we can choose whether we use v1
-     * or v2 protocol. Choice is based on cfg.sshprot.
+     * Decide which SSH protocol version to support.
      */
-    if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
+
+    /* Anything strictly below "2.0" means protocol 1 is supported. */
+    proto1 = ssh_versioncmp(version, "2.0") < 0;
+    /* Anything greater or equal to "1.99" means protocol 2 is supported. */
+    proto2 = ssh_versioncmp(version, "1.99") >= 0;
+
+    if (cfg.sshprot == 0 && !proto1) {
+       bombout(("SSH protocol version 1 required by user but not provided by server"));
+       crReturn(0);
+    }
+    if (cfg.sshprot == 3 && !proto2) {
+       bombout(("SSH protocol version 2 required by user but not provided by server"));
+       crReturn(0);
+    }
+
+    if (proto2 && (cfg.sshprot >= 2 || !proto1)) {
        /*
-        * This is a v2 server. Begin v2 protocol.
+        * Use v2 protocol.
         */
        char verstring[80], vlog[100];
        sprintf(verstring, "SSH-2.0-%s", sshver);
@@ -1543,7 +1860,7 @@ static int do_ssh_init(unsigned char c)
        s_rdpkt = ssh2_rdpkt;
     } else {
        /*
-        * This is a v1 server. Begin v1 protocol.
+        * Use v1 protocol.
         */
        char verstring[80], vlog[100];
        sprintf(verstring, "SSH-%s-%s",
@@ -1552,6 +1869,7 @@ static int do_ssh_init(unsigned char c)
        sprintf(vlog, "We claim version: %s", verstring);
        logevent(vlog);
        strcat(verstring, "\n");
+
        logevent("Using SSH protocol version 1");
        sk_write(s, verstring, strlen(verstring));
        ssh_protocol = ssh1_protocol;
@@ -1621,6 +1939,7 @@ static int ssh_closing(Plug plug, char *error_msg, int error_code,
     }
     if (error_msg) {
        /* A socket error has occurred. */
+       logevent(error_msg);
        connection_fatal(error_msg);
     } else {
        /* Otherwise, the remote side closed the connection normally. */
@@ -1657,7 +1976,7 @@ static void ssh_sent(Plug plug, int bufsize)
  * Also places the canonical host name into `realhost'. It must be
  * freed by the caller.
  */
-static char *connect_to_host(char *host, int port, char **realhost)
+static char *connect_to_host(char *host, int port, char **realhost, int nodelay)
 {
     static struct plug_function_table fn_table = {
        ssh_closing,
@@ -1714,9 +2033,11 @@ static char *connect_to_host(char *host, int port, char **realhost)
        sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
        logevent(buf);
     }
-    s = sk_new(addr, port, 0, 1, &fn_table_ptr);
-    if ((err = sk_socket_error(s)))
+    s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr);
+    if ((err = sk_socket_error(s))) {
+       s = NULL;
        return err;
+    }
 
 #ifdef FWHACK
     sk_write(s, "connect ", 8);
@@ -1782,6 +2103,76 @@ static void ssh_throttle_all(int enable, int bufsize)
 }
 
 /*
+ * Username and password input, abstracted off into reusable
+ * routines (hopefully even reusable between SSH1 and SSH2!).
+ */
+static char *ssh_userpass_input_buffer;
+static int ssh_userpass_input_buflen;
+static int ssh_userpass_input_bufpos;
+static int ssh_userpass_input_echo;
+
+/* Set up a username or password input loop on a given buffer. */
+void setup_userpass_input(char *buffer, int buflen, int echo)
+{
+    ssh_userpass_input_buffer = buffer;
+    ssh_userpass_input_buflen = buflen;
+    ssh_userpass_input_bufpos = 0;
+    ssh_userpass_input_echo = echo;
+}
+
+/*
+ * Process some terminal data in the course of username/password
+ * input. Returns >0 for success (line of input returned in
+ * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0
+ * for inconclusive (keep waiting for more input please).
+ */
+int process_userpass_input(unsigned char *in, int inlen)
+{
+    char c;
+
+    while (inlen--) {
+       switch (c = *in++) {
+         case 10:
+         case 13:
+           ssh_userpass_input_buffer[ssh_userpass_input_bufpos] = 0;
+           ssh_userpass_input_buffer[ssh_userpass_input_buflen-1] = 0;
+           return +1;
+           break;
+         case 8:
+         case 127:
+           if (ssh_userpass_input_bufpos > 0) {
+               if (ssh_userpass_input_echo)
+                   c_write_str("\b \b");
+               ssh_userpass_input_bufpos--;
+           }
+           break;
+         case 21:
+         case 27:
+           while (ssh_userpass_input_bufpos > 0) {
+               if (ssh_userpass_input_echo)
+                   c_write_str("\b \b");
+               ssh_userpass_input_bufpos--;
+           }
+           break;
+         case 3:
+         case 4:
+           return -1;
+           break;
+         default:
+           if (((c >= ' ' && c <= '~') ||
+                ((unsigned char) c >= 160))
+               && ssh_userpass_input_bufpos < ssh_userpass_input_buflen-1) {
+               ssh_userpass_input_buffer[ssh_userpass_input_bufpos++] = c;
+               if (ssh_userpass_input_echo)
+                   c_write(&c, 1);
+           }
+           break;
+       }
+    }
+    return 0;
+}
+
+/*
  * Handle the key exchange and user authentication phases.
  */
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
@@ -1793,11 +2184,13 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
     struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
-    static int tried_publickey;
+    static int tried_publickey, tried_agent;
     static int tis_auth_refused, ccard_auth_refused;
     static unsigned char session_id[16];
     static int cipher_type;
     static char username[100];
+    static void *publickey_blob;
+    int publickey_bloblen;
 
     crBegin;
 
@@ -1964,10 +2357,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     fflush(stdout);
     {
-       static int pos = 0;
-       static char c;
        if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           if (ssh_get_line) {
+           if (ssh_get_line && !ssh_getline_pw_only) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
                    /*
@@ -1979,52 +2370,22 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                    crReturn(1);
                }
            } else {
+               static int ret;
                c_write_str("login as: ");
                ssh_send_ok = 1;
-               while (pos >= 0) {
+
+               setup_userpass_input(username, sizeof(username), 1);
+               do {
                    crWaitUntil(!ispkt);
-                   while (inlen--)
-                       switch (c = *in++) {
-                         case 10:
-                         case 13:
-                           username[pos] = 0;
-                           pos = -1;
-                           break;
-                         case 8:
-                         case 127:
-                           if (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 21:
-                         case 27:
-                           while (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 3:
-                         case 4:
-                           random_save_seed();
-                           exit(0);
-                           break;
-                         default:
-                           if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160))
-                               && pos < sizeof(username)-1) {
-                               username[pos++] = c;
-                               c_write(&c, 1);
-                           }
-                           break;
-                       }
-               }
+                   ret = process_userpass_input(in, inlen);
+               } while (ret == 0);
+               if (ret < 0)
+                   cleanup_exit(0);
                c_write_str("\r\n");
-               username[strcspn(username, "\n\r")] = '\0';
            }
        } else {
-           strncpy(username, cfg.username, 99);
-           username[99] = '\0';
+           strncpy(username, cfg.username, sizeof(username));
+           username[sizeof(username)-1] = '\0';
        }
 
        send_packet(SSH1_CMSG_USER, PKT_STR, username, PKT_END);
@@ -2042,8 +2403,19 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
     crWaitUntil(ispkt);
 
-    tried_publickey = 0;
+    if ((ssh_remote_bugs & BUG_CHOKES_ON_RSA)) {
+       /* We must not attempt PK auth. Pretend we've already tried it. */
+       tried_publickey = tried_agent = 1;
+    } else {
+       tried_publickey = tried_agent = 0;
+    }
     tis_auth_refused = ccard_auth_refused = 0;
+    /* Load the public half of cfg.keyfile so we notice if it's in Pageant */
+    if (*cfg.keyfile) {
+       if (!rsakey_pubblob(cfg.keyfile, &publickey_blob, &publickey_bloblen))
+           publickey_blob = NULL;
+    } else
+       publickey_blob = NULL;
 
     while (pktin.type == SSH1_SMSG_FAILURE) {
        static char password[100];
@@ -2053,7 +2425,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static int pwpkt_type;
        pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
 
-       if (agent_exists()) {
+       if (agent_exists() && !tried_agent) {
            /*
             * Attempt RSA authentication using Pageant.
             */
@@ -2063,6 +2435,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            static int authed = FALSE;
            void *r;
 
+           tried_agent = 1;
            logevent("Pageant is running. Requesting keys.");
 
            /* Request the keys held by the agent. */
@@ -2091,6 +2464,11 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                        sprintf(buf, "Trying Pageant key #%d", i);
                        logevent(buf);
                    }
+                   if (publickey_blob &&
+                       !memcmp(p, publickey_blob, publickey_bloblen)) {
+                       logevent("This key matches configured key file");
+                       tried_publickey = 1;
+                   }
                    p += 4;
                    p += ssh1_read_bignum(p, &key.exponent);
                    p += ssh1_read_bignum(p, &key.modulus);
@@ -2239,8 +2617,22 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        }
        if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
            char *comment = NULL;
+           int type;
+           char msgbuf[256];
            if (flags & FLAG_VERBOSE)
                c_write_str("Trying public key authentication.\r\n");
+           sprintf(msgbuf, "Trying public key \"%.200s\"", cfg.keyfile);
+           logevent(msgbuf);
+           type = key_type(cfg.keyfile);
+           if (type != SSH_KEYTYPE_SSH1) {
+               sprintf(msgbuf, "Key is of wrong type (%s)",
+                       key_type_to_str(type));
+               logevent(msgbuf);
+               c_write_str(msgbuf);
+               c_write_str("\r\n");
+               tried_publickey = 1;
+               continue;
+           }
            if (!rsakey_encrypted(cfg.keyfile, &comment)) {
                if (flags & FLAG_VERBOSE)
                    c_write_str("No passphrase required.\r\n");
@@ -2265,6 +2657,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                send_packet(SSH1_MSG_DISCONNECT,
                            PKT_STR, "No more passwords available to try",
                            PKT_END);
+               logevent("Unable to authenticate");
                connection_fatal("Unable to authenticate");
                ssh_state = SSH_STATE_CLOSED;
                crReturn(1);
@@ -2272,38 +2665,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        } else {
            /* Prompt may have come from server. We've munged it a bit, so
             * we know it to be zero-terminated at least once. */
+           static int ret;
            c_write_untrusted(prompt, strlen(prompt));
            pos = 0;
-           ssh_send_ok = 1;
-           while (pos >= 0) {
+
+           setup_userpass_input(password, sizeof(password), 0);
+           do {
                crWaitUntil(!ispkt);
-               while (inlen--)
-                   switch (c = *in++) {
-                     case 10:
-                     case 13:
-                       password[pos] = 0;
-                       pos = -1;
-                       break;
-                     case 8:
-                     case 127:
-                       if (pos > 0)
-                           pos--;
-                       break;
-                     case 21:
-                     case 27:
-                       pos = 0;
-                       break;
-                     case 3:
-                     case 4:
-                       random_save_seed();
-                       exit(0);
-                       break;
-                     default:
-                       if (pos < sizeof(password)-1)
-                           password[pos++] = c;
-                       break;
-                   }
-           }
+               ret = process_userpass_input(in, inlen);
+           } while (ret == 0);
+           if (ret < 0)
+               cleanup_exit(0);
            c_write_str("\r\n");
        }
 
@@ -2528,7 +2900,7 @@ void sshfwd_close(struct ssh_channel *c)
         * on it now, and then when the server acks the channel
         * open, we can close it then.
         */
-       if (c->remoteid != -1) {
+       if (((int)c->remoteid) != -1) {
            if (ssh_version == 1) {
                send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                            PKT_END);
@@ -2538,7 +2910,7 @@ void sshfwd_close(struct ssh_channel *c)
                ssh2_pkt_send();
            }
        }
-       c->closes = 1;
+       c->closes = 1;                 /* sent MSG_CLOSE */
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
@@ -2640,11 +3012,13 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
     }
 
     {
-       char type, *e;
+       char type;
+       static char *e;
        int n;
-       int sport,dport;
+       int sport,dport,sserv,dserv;
        char sports[256], dports[256], host[256];
        char buf[1024];
+       struct servent *se;
 
        ssh_rportfwds = newtree234(ssh_rportcmp_ssh1);
         /* Add port forwardings. */
@@ -2669,12 +3043,43 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
            dports[n] = 0;
            e++;
            dport = atoi(dports);
+           dserv = 0;
+           if (dport == 0) {
+               dserv = 1;
+               se = getservbyname(dports, NULL);
+               if (se != NULL) {
+                   dport = ntohs(se->s_port);
+               } else {
+                   sprintf(buf,
+                           "Service lookup failed for destination port \"%s\"",
+                           dports);
+                   logevent(buf);
+               }
+           }
            sport = atoi(sports);
+           sserv = 0;
+           if (sport == 0) {
+               sserv = 1;
+               se = getservbyname(sports, NULL);
+               if (se != NULL) {
+                   sport = ntohs(se->s_port);
+               } else {
+                   sprintf(buf,
+                           "Service lookup failed for source port \"%s\"",
+                           sports);
+                   logevent(buf);
+               }
+           }
            if (sport && dport) {
                if (type == 'L') {
                    pfd_addforward(host, dport, sport);
-                   sprintf(buf, "Local port %d forwarding to %s:%d",
-                           sport, host, dport);
+                   sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to"
+                           " %s:%.*s%.*s%d%.*s",
+                           sserv ? strlen(sports) : 0, sports,
+                           sserv, "(", sport, sserv, ")",
+                           host,
+                           dserv ? strlen(dports) : 0, dports,
+                           dserv, "(", dport, dserv, ")");
                    logevent(buf);
                } else {
                    struct ssh_rportfwd *pf;
@@ -2688,14 +3093,31 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        logevent(buf);
                        sfree(pf);
                    } else {
-                       sprintf(buf, "Requesting remote port %d forward to %s:%d",
-                               sport, host, dport);
+                       sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s"
+                               " forward to %s:%.*s%.*s%d%.*s",
+                           sserv ? strlen(sports) : 0, sports,
+                           sserv, "(", sport, sserv, ")",
+                           host,
+                           dserv ? strlen(dports) : 0, dports,
+                           dserv, "(", dport, dserv, ")");
                        logevent(buf);
                        send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST,
                                    PKT_INT, sport,
                                    PKT_STR, host,
                                    PKT_INT, dport,
                                    PKT_END);
+                       do {
+                           crReturnV;
+                       } while (!ispkt);
+                       if (pktin.type != SSH1_SMSG_SUCCESS
+                           && pktin.type != SSH1_SMSG_FAILURE) {
+                           bombout(("Protocol confusion"));
+                           crReturnV;
+                       } else if (pktin.type == SSH1_SMSG_FAILURE) {
+                           c_write_str("Server refused port forwarding\r\n");
+                           ssh_editing = ssh_echoing = 1;
+                       }
+                       logevent("Remote port forwarding enabled");
                    }
                }
            }
@@ -2705,7 +3127,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
-                   PKT_INT, rows, PKT_INT, cols,
+                   PKT_INT, term ? term->rows : 24,
+                   PKT_INT, term ? term->cols : 80,
                    PKT_INT, 0, PKT_INT, 0, PKT_CHAR, 0, PKT_END);
        ssh_state = SSH_STATE_INTERMED;
        do {
@@ -2779,7 +3202,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                pktin.type == SSH1_SMSG_STDERR_DATA) {
                long len = GET_32BIT(pktin.body);
                int bufsize =
-                   from_backend(pktin.type == SSH1_SMSG_STDERR_DATA,
+                   from_backend(frontend, pktin.type == SSH1_SMSG_STDERR_DATA,
                                 pktin.body + 4, len);
                if (!ssh1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
                    ssh1_stdout_throttling = 1;
@@ -2944,13 +3367,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                unsigned i = GET_32BIT(pktin.body);
                struct ssh_channel *c;
                c = find234(ssh_channels, &i, ssh_channelfind);
-               if (c) {
+               if (c && ((int)c->remoteid) != -1) {
                    int closetype;
                    closetype =
                        (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
-                   if (!(c->closes & closetype))
-                       send_packet(pktin.type, PKT_INT, c->remoteid,
-                                   PKT_END);
+
                    if ((c->closes == 0) && (c->type == CHAN_X11)) {
                        logevent("Forwarded X11 connection terminated");
                        assert(c->u.x11.s != NULL);
@@ -2963,11 +3384,23 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        pfd_close(c->u.pfd.s);
                        c->u.pfd.s = NULL;
                    }
-                   c->closes |= closetype;
-                   if (c->closes == 3) {
+
+                   c->closes |= (closetype << 2);   /* seen this message */
+                   if (!(c->closes & closetype)) {
+                       send_packet(pktin.type, PKT_INT, c->remoteid,
+                                   PKT_END);
+                       c->closes |= closetype;      /* sent it too */
+                   }
+
+                   if (c->closes == 15) {
                        del234(ssh_channels, c);
                        sfree(c);
                    }
+               } else {
+                   bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
+                            pktin.type == SSH1_MSG_CHANNEL_CLOSE ? "" :
+                            "_CONFIRMATION", c ? "half-open" : "nonexistent",
+                            i));
                }
            } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) {
                /* Data sent down one of our channels. */
@@ -3050,6 +3483,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                /* may be from EXEC_SHELL on some servers
                 * if no pty is available or in other odd cases. Ignore */
            } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
+               char buf[100];
+               ssh_exitcode = GET_32BIT(pktin.body);
+               sprintf(buf, "Server sent command exit status %d",
+                       ssh_exitcode);
+               logevent(buf);
                send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
                 /*
                  * In case `helpful' firewalls or proxies tack
@@ -3115,14 +3553,16 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr,
     SHA_State s;
     /* First 20 bytes. */
     SHA_Init(&s);
-    sha_mpint(&s, K);
+    if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+       sha_mpint(&s, K);
     SHA_Bytes(&s, H, 20);
     SHA_Bytes(&s, &chr, 1);
     SHA_Bytes(&s, sessid, 20);
     SHA_Final(&s, keyspace);
     /* Next 20 bytes. */
     SHA_Init(&s);
-    sha_mpint(&s, K);
+    if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+       sha_mpint(&s, K);
     SHA_Bytes(&s, H, 20);
     SHA_Bytes(&s, keyspace, 20);
     SHA_Final(&s, keyspace + 20);
@@ -3153,6 +3593,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     static int n_preferred_ciphers;
     static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
     static const struct ssh_compress *preferred_comp;
+    static int cipherstr_started;
     static int first_kex;
 
     crBegin;
@@ -3170,8 +3611,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            n_preferred_ciphers++;
            break;
          case CIPHER_DES:
-           preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
-           n_preferred_ciphers++;
+           if (cfg.ssh2_des_cbc) {
+               preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+               n_preferred_ciphers++;
+           }
            break;
          case CIPHER_3DES:
            preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
@@ -3203,7 +3646,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /*
      * Be prepared to work around the buggy MAC problem.
      */
-    if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
+    if (ssh_remote_bugs & BUG_SSH2_HMAC)
        maclist = buggymacs, nmacs = lenof(buggymacs);
     else
        maclist = macs, nmacs = lenof(macs);
@@ -3218,6 +3661,9 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /* List key exchange algorithms. */
     ssh2_pkt_addstring_start();
     for (i = 0; i < lenof(kex_algs); i++) {
+       if (kex_algs[i] == &ssh_diffiehellman_gex &&
+           (ssh_remote_bugs & BUG_SSH2_DH_GEX))
+           continue;
        ssh2_pkt_addstring_str(kex_algs[i]->name);
        if (i < lenof(kex_algs) - 1)
            ssh2_pkt_addstring_str(",");
@@ -3231,24 +3677,28 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
     /* List client->server encryption algorithms. */
     ssh2_pkt_addstring_start();
+    cipherstr_started = 0;
     for (i = 0; i < n_preferred_ciphers; i++) {
        const struct ssh2_ciphers *c = preferred_ciphers[i];
        if (!c) continue;              /* warning flag */
        for (j = 0; j < c->nciphers; j++) {
-           ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < n_preferred_ciphers || j < c->nciphers - 1)
+           if (cipherstr_started)
                ssh2_pkt_addstring_str(",");
+           ssh2_pkt_addstring_str(c->list[j]->name);
+           cipherstr_started = 1;
        }
     }
     /* List server->client encryption algorithms. */
     ssh2_pkt_addstring_start();
+    cipherstr_started = 0;
     for (i = 0; i < n_preferred_ciphers; i++) {
        const struct ssh2_ciphers *c = preferred_ciphers[i];
        if (!c) continue; /* warning flag */
        for (j = 0; j < c->nciphers; j++) {
-           ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < n_preferred_ciphers || j < c->nciphers - 1)
+           if (cipherstr_started)
                ssh2_pkt_addstring_str(",");
+           ssh2_pkt_addstring_str(c->list[j]->name);
+           cipherstr_started = 1;
        }
     }
     /* List client->server MAC algorithms. */
@@ -3320,6 +3770,9 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     pktin.savedpos += 16;             /* skip garbage cookie */
     ssh2_pkt_getstring(&str, &len);    /* key exchange algorithms */
     for (i = 0; i < lenof(kex_algs); i++) {
+       if (kex_algs[i] == &ssh_diffiehellman_gex &&
+           (ssh_remote_bugs & BUG_SSH2_DH_GEX))
+           continue;
        if (in_commasep_string(kex_algs[i]->name, str, len)) {
            kex = kex_algs[i];
            break;
@@ -3438,6 +3891,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      */
     if (kex == &ssh_diffiehellman_gex) {
        logevent("Doing Diffie-Hellman group exchange");
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGEX;
        /*
         * Work out how big a DH group we will need to allow that
         * much data.
@@ -3458,6 +3912,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
        kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
        kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
     } else {
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
        dh_setup_group1();
        kex_init_value = SSH2_MSG_KEXDH_INIT;
        kex_reply_value = SSH2_MSG_KEXDH_REPLY;
@@ -3640,6 +4095,14 @@ static int ssh2_try_send(struct ssh_channel *c)
  */
 static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
 {
+    /*
+     * Never send WINDOW_ADJUST for a channel that the remote side
+     * already thinks it's closed; there's no point, since it won't
+     * be sending any more data anyway.
+     */
+    if (c->closes != 0)
+       return;
+
     if (newwin > c->v.v2.locwindow) {
        ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
        ssh2_pkt_adduint32(c->remoteid);
@@ -3672,10 +4135,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     static int tried_pubkey_config, tried_agent, tried_keyb_inter;
     static int kbd_inter_running;
     static int we_are_in;
-    static int num_prompts, echo;
+    static int num_prompts, curr_prompt, echo;
     static char username[100];
+    static int got_username;
     static char pwprompt[200];
     static char password[100];
+    static void *publickey_blob;
+    static int publickey_bloblen;
 
     crBegin;
 
@@ -3715,16 +4181,20 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      *    the username they will want to be able to get back and
      *    retype it!
      */
+    username[0] = '\0';
+    got_username = FALSE;
     do {
-       static int pos;
-       static char c;
-
        /*
         * Get a username.
         */
-       pos = 0;
-       if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           if (ssh_get_line) {
+       if (got_username && !cfg.change_username) {
+           /*
+            * We got a username last time round this loop, and
+            * with change_username turned off we don't try to get
+            * it again.
+            */
+       } else if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+           if (ssh_get_line && !ssh_getline_pw_only) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
                    /*
@@ -3736,64 +4206,37 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    crReturnV;
                }
            } else {
+               static int ret;
                c_write_str("login as: ");
                ssh_send_ok = 1;
-               while (pos >= 0) {
+               setup_userpass_input(username, sizeof(username), 1);
+               do {
                    crWaitUntilV(!ispkt);
-                   while (inlen--)
-                       switch (c = *in++) {
-                         case 10:
-                         case 13:
-                           username[pos] = 0;
-                           pos = -1;
-                           break;
-                         case 8:
-                         case 127:
-                           if (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 21:
-                         case 27:
-                           while (pos > 0) {
-                               c_write_str("\b \b");
-                               pos--;
-                           }
-                           break;
-                         case 3:
-                         case 4:
-                           random_save_seed();
-                           exit(0);
-                           break;
-                         default:
-                           if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160))
-                               && pos < sizeof(username)-1) {
-                               username[pos++] = c;
-                               c_write(&c, 1);
-                           }
-                           break;
-                       }
-               }
+                   ret = process_userpass_input(in, inlen);
+               } while (ret == 0);
+               if (ret < 0)
+                   cleanup_exit(0);
            }
            c_write_str("\r\n");
            username[strcspn(username, "\n\r")] = '\0';
        } else {
            char stuff[200];
-           strncpy(username, cfg.username, 99);
-           username[99] = '\0';
+           strncpy(username, cfg.username, sizeof(username));
+           username[sizeof(username)-1] = '\0';
            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
                sprintf(stuff, "Using username \"%s\".\r\n", username);
                c_write_str(stuff);
            }
        }
+       got_username = TRUE;
 
        /*
         * Send an authentication request using method "none": (a)
         * just in case it succeeds, and (b) so that we know what
         * authentication methods we can usefully try next.
         */
+       ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
        ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
        ssh2_pkt_addstring(username);
        ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -3807,6 +4250,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        tried_agent = FALSE;
        tried_keyb_inter = FALSE;
        kbd_inter_running = FALSE;
+       /* Load the pub half of cfg.keyfile so we notice if it's in Pageant */
+       if (*cfg.keyfile) {
+           int keytype;
+           logeventf("Reading private key file \"%.150s\"", cfg.keyfile);
+           keytype = key_type(cfg.keyfile);
+           if (keytype == SSH_KEYTYPE_SSH2)
+               publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL,
+                                                     &publickey_bloblen);
+           else {
+               char msgbuf[256];
+               logeventf("Unable to use this key file (%s)",
+                       key_type_to_str(keytype));
+               sprintf(msgbuf, "Unable to use key file \"%.150s\" (%s)\r\n",
+                       cfg.keyfile, key_type_to_str(keytype));
+               c_write_str(msgbuf);
+               publickey_blob = NULL;
+           }
+       } else
+           publickey_blob = NULL;
 
        while (1) {
            /*
@@ -3841,9 +4303,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            if (kbd_inter_running &&
                pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
                /*
-                * This is a further prompt in keyboard-interactive
-                * authentication. Do nothing.
+                * This is either a further set-of-prompts packet
+                * in keyboard-interactive authentication, or it's
+                * the same one and we came back here with `gotit'
+                * set. In the former case, we must reset the
+                * curr_prompt variable.
                 */
+               if (!gotit)
+                   curr_prompt = 0;
            } else if (pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
                bombout(("Strange packet received during authentication: type %d",
                         pktin.type));
@@ -3910,11 +4377,20 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    in_commasep_string("publickey", methods, methlen);
                can_passwd =
                    in_commasep_string("password", methods, methlen);
-               can_keyb_inter = 
+               can_keyb_inter = cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
            }
 
            method = 0;
+           ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
+           /*
+            * Most password/passphrase prompts will be
+            * non-echoing, so we set this to 0 by default.
+            * Exception is that some keyboard-interactive prompts
+            * can be echoing, in which case we'll set this to 1.
+            */
+           echo = 0;
 
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
@@ -3926,6 +4402,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                static int authed = FALSE;
                void *r;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                tried_agent = TRUE;
 
                logevent("Pageant is running. Requesting keys.");
@@ -3959,6 +4438,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        }
                        pklen = GET_32BIT(p);
                        p += 4;
+                       if (publickey_blob &&
+                           pklen == publickey_bloblen &&
+                           !memcmp(p, publickey_blob, publickey_bloblen)) {
+                           logevent("This key matches configured key file");
+                           tried_pubkey_config = 1;
+                       }
                        pkblob = p;
                        p += pklen;
                        alglen = GET_32BIT(pkblob);
@@ -4035,10 +4520,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        if (ret) {
                            if (ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
                                logevent("Sending Pageant's response");
-                               ssh2_pkt_addstring_start();
-                               ssh2_pkt_addstring_data(ret + 9,
-                                                       GET_32BIT(ret +
-                                                                 5));
+                               ssh2_add_sigblob(pkblob, pklen,
+                                                ret + 9, GET_32BIT(ret + 5));
                                ssh2_pkt_send();
                                authed = TRUE;
                                break;
@@ -4054,7 +4537,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
            }
 
-           if (!method && can_pubkey && *cfg.keyfile
+           if (!method && can_pubkey && publickey_blob
                && !tried_pubkey_config) {
                unsigned char *pub_blob;
                char *algorithm, *comment;
@@ -4062,6 +4545,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                tried_pubkey_config = TRUE;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                /*
                 * Try the public key supplied in the configuration.
                 *
@@ -4114,6 +4600,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
                ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(username);
                ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -4132,6 +4621,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
 
                kbd_inter_running = TRUE;
+               curr_prompt = 0;
            }
 
            if (kbd_inter_running) {
@@ -4139,32 +4629,59 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
-               /* We've got packet with that "interactive" info
-                  dump banners, and set its prompt as ours */
-               {
-                   char *name, *inst, *lang, *prompt;
-                   int name_len, inst_len, lang_len, prompt_len;
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+               if (curr_prompt == 0) {
+                   /*
+                    * We've got a fresh USERAUTH_INFO_REQUEST.
+                    * Display header data, and start going through
+                    * the prompts.
+                    */
+                   char *name, *inst, *lang;
+                   int name_len, inst_len, lang_len;
+
                    ssh2_pkt_getstring(&name, &name_len);
                    ssh2_pkt_getstring(&inst, &inst_len);
                    ssh2_pkt_getstring(&lang, &lang_len);
-                   if (name_len > 0)
+                   if (name_len > 0) {
                        c_write_untrusted(name, name_len);
-                   if (inst_len > 0)
+                       c_write_str("\r\n");
+                   }
+                   if (inst_len > 0) {
                        c_write_untrusted(inst, inst_len);
+                       c_write_str("\r\n");
+                   }
                    num_prompts = ssh2_pkt_getuint32();
+               }
 
-                   ssh2_pkt_getstring(&prompt, &prompt_len);
-                   strncpy(pwprompt, prompt, sizeof(pwprompt));
-                   pwprompt[prompt_len < sizeof(pwprompt) ?
-                            prompt_len : sizeof(pwprompt)-1] = '\0';
-                   need_pw = TRUE;
+               /*
+                * If there are prompts remaining in the packet,
+                * display one and get a response.
+                */
+               if (curr_prompt < num_prompts) {
+                   char *prompt;
+                   int prompt_len;
 
+                   ssh2_pkt_getstring(&prompt, &prompt_len);
+                   if (prompt_len > 0) {
+                       strncpy(pwprompt, prompt, sizeof(pwprompt));
+                       pwprompt[prompt_len < sizeof(pwprompt) ?
+                                prompt_len : sizeof(pwprompt)-1] = '\0';
+                   } else {
+                       strcpy(pwprompt,
+                              "<server failed to send prompt>: ");
+                   }
                    echo = ssh2_pkt_getbool();
-               }
+                   need_pw = TRUE;
+               } else
+                   need_pw = FALSE;
            }
 
            if (!method && can_passwd) {
                method = AUTH_PASSWORD;
+               ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+               ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD;
                sprintf(pwprompt, "%.90s@%.90s's password: ", username,
                        savedhost);
                need_pw = TRUE;
@@ -4186,47 +4703,23 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                            ("No more passwords available to try");
                        ssh2_pkt_addstring("en");       /* language tag */
                        ssh2_pkt_send();
+                       logevent("Unable to authenticate");
                        connection_fatal("Unable to authenticate");
                        ssh_state = SSH_STATE_CLOSED;
                        crReturnV;
                    }
                } else {
-                   static int pos = 0;
-                   static char c;
-
+                   static int ret;
                    c_write_untrusted(pwprompt, strlen(pwprompt));
                    ssh_send_ok = 1;
 
-                   pos = 0;
-                   while (pos >= 0) {
+                   setup_userpass_input(password, sizeof(password), echo);
+                   do {
                        crWaitUntilV(!ispkt);
-                       while (inlen--)
-                           switch (c = *in++) {
-                             case 10:
-                             case 13:
-                               password[pos] = 0;
-                               pos = -1;
-                               break;
-                             case 8:
-                             case 127:
-                               if (pos > 0)
-                                   pos--;
-                               break;
-                             case 21:
-                             case 27:
-                               pos = 0;
-                               break;
-                             case 3:
-                             case 4:
-                               random_save_seed();
-                               exit(0);
-                               break;
-                             default:
-                               if (pos < sizeof(password)-1)
-                                   password[pos++] = c;
-                               break;
-                           }
-                   }
+                       ret = process_userpass_input(in, inlen);
+                   } while (ret == 0);
+                   if (ret < 0)
+                       cleanup_exit(0);
                    c_write_str("\r\n");
                }
            }
@@ -4254,8 +4747,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    ssh2_pkt_send();
                    type = AUTH_TYPE_NONE;
                } else {
-                   unsigned char *blob, *sigdata;
-                   int blob_len, sigdata_len;
+                   unsigned char *pkblob, *sigblob, *sigdata;
+                   int pkblob_len, sigblob_len, sigdata_len;
 
                    /*
                     * We have loaded the private key and the server
@@ -4268,10 +4761,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    ssh2_pkt_addstring("publickey");    /* method */
                    ssh2_pkt_addbool(TRUE);
                    ssh2_pkt_addstring(key->alg->name);
-                   blob = key->alg->public_blob(key->data, &blob_len);
+                   pkblob = key->alg->public_blob(key->data, &pkblob_len);
                    ssh2_pkt_addstring_start();
-                   ssh2_pkt_addstring_data(blob, blob_len);
-                   sfree(blob);
+                   ssh2_pkt_addstring_data(pkblob, pkblob_len);
 
                    /*
                     * The data to be signed is:
@@ -4287,12 +4779,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    memcpy(sigdata + 4, ssh2_session_id, 20);
                    memcpy(sigdata + 24, pktout.data + 5,
                           pktout.length - 5);
-                   blob =
-                       key->alg->sign(key->data, sigdata, sigdata_len,
-                                      &blob_len);
-                   ssh2_pkt_addstring_start();
-                   ssh2_pkt_addstring_data(blob, blob_len);
-                   sfree(blob);
+                   sigblob = key->alg->sign(key->data, sigdata,
+                                            sigdata_len, &sigblob_len);
+                   ssh2_add_sigblob(pkblob, pkblob_len,
+                                    sigblob, sigblob_len);
+                   sfree(pkblob);
+                   sfree(sigblob);
                    sfree(sigdata);
 
                    ssh2_pkt_send();
@@ -4356,11 +4848,28 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                logevent("Sent password");
                type = AUTH_TYPE_PASSWORD;
            } else if (method == AUTH_KEYBOARD_INTERACTIVE) {
-                ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
-                ssh2_pkt_adduint32(num_prompts);
-                ssh2_pkt_addstring(password);
-                memset(password, 0, sizeof(password));
-                ssh2_pkt_send();
+               if (curr_prompt == 0) {
+                   ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+                   ssh2_pkt_adduint32(num_prompts);
+               }
+               if (need_pw) {         /* only add pw if we just got one! */
+                   ssh2_pkt_addstring(password);
+                   memset(password, 0, sizeof(password));
+                   curr_prompt++;
+               }
+               if (curr_prompt >= num_prompts) {
+                   ssh2_pkt_send();
+               } else {
+                   /*
+                    * If there are prompts remaining, we set
+                    * `gotit' so that we won't attempt to get
+                    * another packet. Then we go back round the
+                    * loop and will end up retrieving another
+                    * prompt out of the existing packet. Funky or
+                    * what?
+                    */
+                   gotit = TRUE;
+               }
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
            } else {
                c_write_str
@@ -4466,9 +4975,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        static char *e;                /* preserve across crReturn */
        char type;
        int n;
-       int sport,dport;
+       int sport,dport,sserv,dserv;
        char sports[256], dports[256], host[256];
        char buf[1024];
+       struct servent *se;
 
        ssh_rportfwds = newtree234(ssh_rportcmp_ssh2);
         /* Add port forwardings. */
@@ -4493,12 +5003,43 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            dports[n] = 0;
            e++;
            dport = atoi(dports);
+           dserv = 0;
+           if (dport == 0) {
+               dserv = 1;
+               se = getservbyname(dports, NULL);
+               if (se != NULL) {
+                   dport = ntohs(se->s_port);
+               } else {
+                   sprintf(buf,
+                           "Service lookup failed for destination port \"%s\"",
+                           dports);
+                   logevent(buf);
+               }
+           }
            sport = atoi(sports);
+           sserv = 0;
+           if (sport == 0) {
+               sserv = 1;
+               se = getservbyname(sports, NULL);
+               if (se != NULL) {
+                   sport = ntohs(se->s_port);
+               } else {
+                   sprintf(buf,
+                           "Service lookup failed for source port \"%s\"",
+                           sports);
+                   logevent(buf);
+               }
+           }
            if (sport && dport) {
                if (type == 'L') {
                    pfd_addforward(host, dport, sport);
-                   sprintf(buf, "Local port %d forwarding to %s:%d",
-                           sport, host, dport);
+                   sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to"
+                           " %s:%.*s%.*s%d%.*s",
+                           sserv ? strlen(sports) : 0, sports,
+                           sserv, "(", sport, sserv, ")",
+                           host,
+                           dserv ? strlen(dports) : 0, dports,
+                           dserv, "(", dport, dserv, ")");
                    logevent(buf);
                } else {
                    struct ssh_rportfwd *pf;
@@ -4513,13 +5054,21 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        logevent(buf);
                        sfree(pf);
                    } else {
-                       sprintf(buf, "Requesting remote port %d (forwarded to %s:%d)",
-                               sport, host, dport);
+                       sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s"
+                               " forward to %s:%.*s%.*s%d%.*s",
+                           sserv ? strlen(sports) : 0, sports,
+                           sserv, "(", sport, sserv, ")",
+                           host,
+                           dserv ? strlen(dports) : 0, dports,
+                           dserv, "(", dport, dserv, ")");
                        logevent(buf);
                        ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
                        ssh2_pkt_addstring("tcpip-forward");
                        ssh2_pkt_addbool(1);/* want reply */
-                       ssh2_pkt_addstring("127.0.0.1");
+                       if (cfg.rport_acceptall)
+                           ssh2_pkt_addstring("0.0.0.0");
+                       else
+                           ssh2_pkt_addstring("127.0.0.1");
                        ssh2_pkt_adduint32(sport);
                        ssh2_pkt_send();
 
@@ -4597,8 +5146,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        ssh2_pkt_addstring("pty-req");
        ssh2_pkt_addbool(1);           /* want reply */
        ssh2_pkt_addstring(cfg.termtype);
-       ssh2_pkt_adduint32(cols);
-       ssh2_pkt_adduint32(rows);
+       ssh2_pkt_adduint32(term ? term->cols : 80);
+       ssh2_pkt_adduint32(term ? term->rows : 24);
        ssh2_pkt_adduint32(0);         /* pixel width */
        ssh2_pkt_adduint32(0);         /* pixel height */
        ssh2_pkt_addstring_start();
@@ -4736,7 +5285,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    switch (c->type) {
                      case CHAN_MAINSESSION:
                        bufsize =
-                           from_backend(pktin.type ==
+                           from_backend(frontend, pktin.type ==
                                         SSH2_MSG_CHANNEL_EXTENDED_DATA,
                                         data, length);
                        break;
@@ -4834,8 +5383,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                struct ssh_channel *c;
 
                c = find234(ssh_channels, &i, ssh_channelfind);
-               if (!c)
-                   continue;          /* nonexistent channel */
+               if (!c || ((int)c->remoteid) == -1) {
+                   bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
+                            c ? "half-open" : "nonexistent", i));
+               }
                /* Do pre-close processing on the channel. */
                switch (c->type) {
                  case CHAN_MAINSESSION:
@@ -4867,12 +5418,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 * See if that was the last channel left open.
                 */
                if (count234(ssh_channels) == 0) {
+#if 0
+                    /*
+                     * We used to send SSH_MSG_DISCONNECT here,
+                     * because I'd believed that _every_ conforming
+                     * SSH2 connection had to end with a disconnect
+                     * being sent by at least one side; apparently
+                     * I was wrong and it's perfectly OK to
+                     * unceremoniously slam the connection shut
+                     * when you're done, and indeed OpenSSH feels
+                     * this is more polite than sending a
+                     * DISCONNECT. So now we don't.
+                     */
                    logevent("All channels closed. Disconnecting");
                    ssh2_pkt_init(SSH2_MSG_DISCONNECT);
                    ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
                    ssh2_pkt_addstring("All open channels closed");
                    ssh2_pkt_addstring("en");   /* language tag */
                    ssh2_pkt_send();
+#endif
                    ssh_state = SSH_STATE_CLOSED;
                    crReturnV;
                }
@@ -4897,7 +5461,6 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                c->type = CHAN_SOCKDATA;
                c->v.v2.remwindow = ssh2_pkt_getuint32();
                c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
-               bufchain_init(&c->v.v2.outbuffer);
                if (c->u.pfd.s)
                    pfd_confirm(c->u.pfd.s);
                if (c->closes) {
@@ -4951,20 +5514,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    ssh2_pkt_addstring(buf);
                    ssh2_pkt_addstring("en");   /* language tag */
                    ssh2_pkt_send();
-                   connection_fatal(buf);
+                   connection_fatal("%s", buf);
                    ssh_state = SSH_STATE_CLOSED;
                    crReturnV;
                }
 
                /*
-                * We don't recognise any form of channel request,
-                * so we now either ignore the request or respond
-                * with CHANNEL_FAILURE, depending on want_reply.
+                * Having got the channel number, we now look at
+                * the request type string to see if it's something
+                * we recognise.
                 */
-               if (want_reply) {
-                   ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
-                   ssh2_pkt_adduint32(c->remoteid);
-                   ssh2_pkt_send();
+               if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+                   c == mainchan) {
+                   /* We recognise "exit-status" on the primary channel. */
+                   char buf[100];
+                   ssh_exitcode = ssh2_pkt_getuint32();
+                   sprintf(buf, "Server sent command exit status %d",
+                           ssh_exitcode);
+                   logevent(buf);
+                   if (want_reply) {
+                       ssh2_pkt_init(SSH2_MSG_CHANNEL_SUCCESS);
+                       ssh2_pkt_adduint32(c->remoteid);
+                       ssh2_pkt_send();
+                   }
+               } else {
+                   /*
+                    * This is a channel request we don't know
+                    * about, so we now either ignore the request
+                    * or respond with CHANNEL_FAILURE, depending
+                    * on want_reply.
+                    */
+                   if (want_reply) {
+                       ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+                       ssh2_pkt_adduint32(c->remoteid);
+                       ssh2_pkt_send();
+                   }
+               }
+           } else if (pktin.type == SSH2_MSG_GLOBAL_REQUEST) {
+               char *type;
+               int typelen, want_reply;
+
+               ssh2_pkt_getstring(&type, &typelen);
+               want_reply = ssh2_pkt_getbool();
+
+                /*
+                 * We currently don't support any global requests
+                 * at all, so we either ignore the request or
+                 * respond with REQUEST_FAILURE, depending on
+                 * want_reply.
+                 */
+                if (want_reply) {
+                    ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
+                    ssh2_pkt_send();
                }
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
                char *type;
@@ -5109,7 +5710,8 @@ static void ssh2_protocol(unsigned char *in, int inlen, int ispkt)
  *
  * Returns an error message, or NULL on success.
  */
-static char *ssh_init(char *host, int port, char **realhost)
+static char *ssh_init(void *frontend_handle,
+                     char *host, int port, char **realhost, int nodelay)
 {
     char *p;
 
@@ -5118,6 +5720,8 @@ static char *ssh_init(char *host, int port, char **realhost)
        return "Microsoft high encryption pack not installed!";
 #endif
 
+    frontend = frontend_handle;
+
     ssh_send_ok = 0;
     ssh_editing = 0;
     ssh_echoing = 0;
@@ -5125,7 +5729,7 @@ static char *ssh_init(char *host, int port, char **realhost)
     ssh_overall_bufsize = 0;
     ssh_fallback_cmd = 0;
 
-    p = connect_to_host(host, port, realhost);
+    p = connect_to_host(host, port, realhost, nodelay);
     if (p != NULL)
        return p;
 
@@ -5190,17 +5794,19 @@ static void ssh_size(void)
        break;
       case SSH_STATE_SESSION:
        if (!cfg.nopty) {
+           if (!term)
+               return;
            if (ssh_version == 1) {
                send_packet(SSH1_CMSG_WINDOW_SIZE,
-                           PKT_INT, rows, PKT_INT, cols,
+                           PKT_INT, term->rows, PKT_INT, term->cols,
                            PKT_INT, 0, PKT_INT, 0, PKT_END);
            } else {
                ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
                ssh2_pkt_adduint32(mainchan->remoteid);
                ssh2_pkt_addstring("window-change");
                ssh2_pkt_addbool(0);
-               ssh2_pkt_adduint32(cols);
-               ssh2_pkt_adduint32(rows);
+               ssh2_pkt_adduint32(term->cols);
+               ssh2_pkt_adduint32(term->rows);
                ssh2_pkt_adduint32(0);
                ssh2_pkt_adduint32(0);
                ssh2_pkt_send();
@@ -5239,7 +5845,8 @@ static void ssh_special(Telnet_Special code)
        if (ssh_state == SSH_STATE_CLOSED
            || ssh_state == SSH_STATE_PREPACKET) return;
        if (ssh_version == 1) {
-           send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
+           if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+               send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
        } else {
            ssh2_pkt_init(SSH2_MSG_IGNORE);
            ssh2_pkt_addstring_start();
@@ -5261,6 +5868,7 @@ void *new_sock_channel(Socket s)
        c->closes = 0;
        c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
        c->u.pfd.s = s;
+       bufchain_init(&c->v.v2.outbuffer);
        add234(ssh_channels, c);
     }
     return c;
@@ -5339,6 +5947,11 @@ static int ssh_ldisc(int option)
     return FALSE;
 }
 
+static int ssh_return_exitcode(void)
+{
+    return ssh_exitcode;
+}
+
 Backend ssh_backend = {
     ssh_init,
     ssh_send,
@@ -5346,6 +5959,7 @@ Backend ssh_backend = {
     ssh_size,
     ssh_special,
     ssh_socket,
+    ssh_return_exitcode,
     ssh_sendok,
     ssh_ldisc,
     ssh_unthrottle,