]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - ssh.c
Support OpenSSH encrypt-then-MAC protocol extension.
[PuTTY.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index b4c634d310c363930e7ecb5fda19358af1a107b3..5ed63cc24decc2f27b1658cdeff4efde87fcc929 100644 (file)
--- a/ssh.c
+++ b/ssh.c
 #define TRUE 1
 #endif
 
-#define SSH1_MSG_DISCONNECT                       1    /* 0x1 */
-#define SSH1_SMSG_PUBLIC_KEY                      2    /* 0x2 */
-#define SSH1_CMSG_SESSION_KEY                     3    /* 0x3 */
-#define SSH1_CMSG_USER                            4    /* 0x4 */
-#define SSH1_CMSG_AUTH_RSA                        6    /* 0x6 */
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7    /* 0x7 */
-#define SSH1_CMSG_AUTH_RSA_RESPONSE               8    /* 0x8 */
-#define SSH1_CMSG_AUTH_PASSWORD                   9    /* 0x9 */
-#define SSH1_CMSG_REQUEST_PTY                     10   /* 0xa */
-#define SSH1_CMSG_WINDOW_SIZE                     11   /* 0xb */
-#define SSH1_CMSG_EXEC_SHELL                      12   /* 0xc */
-#define SSH1_CMSG_EXEC_CMD                        13   /* 0xd */
-#define SSH1_SMSG_SUCCESS                         14   /* 0xe */
-#define SSH1_SMSG_FAILURE                         15   /* 0xf */
-#define SSH1_CMSG_STDIN_DATA                      16   /* 0x10 */
-#define SSH1_SMSG_STDOUT_DATA                     17   /* 0x11 */
-#define SSH1_SMSG_STDERR_DATA                     18   /* 0x12 */
-#define SSH1_CMSG_EOF                             19   /* 0x13 */
-#define SSH1_SMSG_EXIT_STATUS                     20   /* 0x14 */
-#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21   /* 0x15 */
-#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22   /* 0x16 */
-#define SSH1_MSG_CHANNEL_DATA                     23   /* 0x17 */
-#define SSH1_MSG_CHANNEL_CLOSE                    24   /* 0x18 */
-#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25   /* 0x19 */
-#define SSH1_SMSG_X11_OPEN                        27   /* 0x1b */
-#define SSH1_CMSG_PORT_FORWARD_REQUEST            28   /* 0x1c */
-#define SSH1_MSG_PORT_OPEN                        29   /* 0x1d */
-#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30   /* 0x1e */
-#define SSH1_SMSG_AGENT_OPEN                      31   /* 0x1f */
-#define SSH1_MSG_IGNORE                           32   /* 0x20 */
-#define SSH1_CMSG_EXIT_CONFIRMATION               33   /* 0x21 */
-#define SSH1_CMSG_X11_REQUEST_FORWARDING          34   /* 0x22 */
-#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35   /* 0x23 */
-#define SSH1_MSG_DEBUG                            36   /* 0x24 */
-#define SSH1_CMSG_REQUEST_COMPRESSION             37   /* 0x25 */
-#define SSH1_CMSG_AUTH_TIS                        39   /* 0x27 */
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40   /* 0x28 */
-#define SSH1_CMSG_AUTH_TIS_RESPONSE               41   /* 0x29 */
-#define SSH1_CMSG_AUTH_CCARD                      70   /* 0x46 */
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71   /* 0x47 */
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72   /* 0x48 */
-
-#define SSH1_AUTH_RHOSTS                          1    /* 0x1 */
-#define SSH1_AUTH_RSA                             2    /* 0x2 */
-#define SSH1_AUTH_PASSWORD                        3    /* 0x3 */
-#define SSH1_AUTH_RHOSTS_RSA                      4    /* 0x4 */
-#define SSH1_AUTH_TIS                             5    /* 0x5 */
-#define SSH1_AUTH_CCARD                           16   /* 0x10 */
-
-#define SSH1_PROTOFLAG_SCREEN_NUMBER              1    /* 0x1 */
-/* Mask for protoflags we will echo back to server if seen */
-#define SSH1_PROTOFLAGS_SUPPORTED                 0    /* 0x1 */
-
-#define SSH2_MSG_DISCONNECT                       1    /* 0x1 */
-#define SSH2_MSG_IGNORE                           2    /* 0x2 */
-#define SSH2_MSG_UNIMPLEMENTED                    3    /* 0x3 */
-#define SSH2_MSG_DEBUG                            4    /* 0x4 */
-#define SSH2_MSG_SERVICE_REQUEST                  5    /* 0x5 */
-#define SSH2_MSG_SERVICE_ACCEPT                   6    /* 0x6 */
-#define SSH2_MSG_KEXINIT                          20   /* 0x14 */
-#define SSH2_MSG_NEWKEYS                          21   /* 0x15 */
-#define SSH2_MSG_KEXDH_INIT                       30   /* 0x1e */
-#define SSH2_MSG_KEXDH_REPLY                      31   /* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST               30   /* 0x1e */
-#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_MSG_USERAUTH_BANNER                  53   /* 0x35 */
-#define SSH2_MSG_USERAUTH_PK_OK                   60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_REQUEST            60   /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61   /* 0x3d */
-#define SSH2_MSG_GLOBAL_REQUEST                   80   /* 0x50 */
-#define SSH2_MSG_REQUEST_SUCCESS                  81   /* 0x51 */
-#define SSH2_MSG_REQUEST_FAILURE                  82   /* 0x52 */
-#define SSH2_MSG_CHANNEL_OPEN                     90   /* 0x5a */
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91   /* 0x5b */
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92   /* 0x5c */
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93   /* 0x5d */
-#define SSH2_MSG_CHANNEL_DATA                     94   /* 0x5e */
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95   /* 0x5f */
-#define SSH2_MSG_CHANNEL_EOF                      96   /* 0x60 */
-#define SSH2_MSG_CHANNEL_CLOSE                    97   /* 0x61 */
-#define SSH2_MSG_CHANNEL_REQUEST                  98   /* 0x62 */
-#define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
-#define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
-#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE               60
-#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN                  61
-#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE      63
-#define SSH2_MSG_USERAUTH_GSSAPI_ERROR                  64
-#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK                 65
-#define SSH2_MSG_USERAUTH_GSSAPI_MIC                    66
-
 /*
  * Packet type contexts, so that ssh2_pkt_type can correctly decode
  * the ambiguous type numbers back into the correct type strings.
@@ -131,6 +32,7 @@ typedef enum {
     SSH2_PKTCTX_NOKEX,
     SSH2_PKTCTX_DHGROUP,
     SSH2_PKTCTX_DHGEX,
+    SSH2_PKTCTX_ECDHKEX,
     SSH2_PKTCTX_RSAKEX
 } Pkt_KCtx;
 typedef enum {
@@ -141,22 +43,6 @@ typedef enum {
     SSH2_PKTCTX_KBDINTER
 } Pkt_ACtx;
 
-#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 */
-#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4   /* 0x4 */
-#define SSH2_DISCONNECT_MAC_ERROR                 5    /* 0x5 */
-#define SSH2_DISCONNECT_COMPRESSION_ERROR         6    /* 0x6 */
-#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE     7    /* 0x7 */
-#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8       /* 0x8 */
-#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE   9    /* 0x9 */
-#define SSH2_DISCONNECT_CONNECTION_LOST           10   /* 0xa */
-#define SSH2_DISCONNECT_BY_APPLICATION            11   /* 0xb */
-#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS      12   /* 0xc */
-#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER    13   /* 0xd */
-#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14      /* 0xe */
-#define SSH2_DISCONNECT_ILLEGAL_USER_NAME         15   /* 0xf */
-
 static const char *const ssh2_disconnect_reasons[] = {
     NULL,
     "host not allowed to connect",
@@ -176,13 +62,6 @@ static const char *const ssh2_disconnect_reasons[] = {
     "illegal user name",
 };
 
-#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1    /* 0x1 */
-#define SSH2_OPEN_CONNECT_FAILED                  2    /* 0x2 */
-#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE            3    /* 0x3 */
-#define SSH2_OPEN_RESOURCE_SHORTAGE               4    /* 0x4 */
-
-#define SSH2_EXTENDED_DATA_STDERR                 1    /* 0x1 */
-
 /*
  * Various remote-bug flags.
  */
@@ -196,6 +75,12 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_PK_SESSIONID                   128
 #define BUG_SSH2_MAXPKT                                256
 #define BUG_CHOKES_ON_SSH2_IGNORE               512
+#define BUG_CHOKES_ON_WINADJ                   1024
+#define BUG_SENDS_LATE_REQUEST_REPLY           2048
+#define BUG_SSH2_OLDGEX                        4096
+
+#define DH_MIN_SIZE 1024
+#define DH_MAX_SIZE 8192
 
 /*
  * Codes for terminal modes.
@@ -367,6 +252,7 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
     translate(SSH2_MSG_NEWKEYS);
     translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
     translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
+    translatek(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, SSH2_PKTCTX_DHGEX);
     translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
     translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
     translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
@@ -374,6 +260,8 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
     translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
     translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
     translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
+    translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX);
+    translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX);
     translate(SSH2_MSG_USERAUTH_REQUEST);
     translate(SSH2_MSG_USERAUTH_FAILURE);
     translate(SSH2_MSG_USERAUTH_SUCCESS);
@@ -404,9 +292,6 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
 /* Enumeration values for fields in SSH-1 packets */
 enum {
     PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM,
-    /* These values are for communicating relevant semantics of
-     * fields to the packet logging code. */
-    PKTT_OTHER, PKTT_PASSWORD, PKTT_DATA
 };
 
 /*
@@ -430,12 +315,16 @@ enum {
  *    Database for Edit and Continue'.
  */
 #define crBegin(v)     { int *crLine = &v; switch(v) { case 0:;
-#define crState(t) \
-    struct t *s; \
-    if (!ssh->t) ssh->t = snew(struct t); \
-    s = ssh->t;
+#define crBeginState   crBegin(s->crLine)
+#define crStateP(t, v)                         \
+    struct t *s;                               \
+    if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; }     \
+    s = (v);
+#define crState(t)     crStateP(t, ssh->t)
 #define crFinish(z)    } *crLine = 0; return (z); }
 #define crFinishV      } *crLine = 0; return; }
+#define crFinishFree(z)        } sfree(s); return (z); }
+#define crFinishFreeV  } sfree(s); return; }
 #define crReturn(z)    \
        do {\
            *crLine =__LINE__; return (z); case __LINE__:;\
@@ -449,20 +338,19 @@ enum {
 #define crWaitUntil(c) do { crReturn(0); } while (!(c))
 #define crWaitUntilV(c)        do { crReturnV; } while (!(c))
 
-typedef struct ssh_tag *Ssh;
 struct Packet;
 
 static struct Packet *ssh1_pkt_init(int pkt_type);
 static struct Packet *ssh2_pkt_init(int pkt_type);
 static void ssh_pkt_ensure(struct Packet *, int length);
-static void ssh_pkt_adddata(struct Packet *, void *data, int len);
+static void ssh_pkt_adddata(struct Packet *, const void *data, int len);
 static void ssh_pkt_addbyte(struct Packet *, unsigned char value);
 static void ssh2_pkt_addbool(struct Packet *, unsigned char value);
 static void ssh_pkt_adduint32(struct Packet *, unsigned long value);
 static void ssh_pkt_addstring_start(struct Packet *);
-static void ssh_pkt_addstring_str(struct Packet *, char *data);
-static void ssh_pkt_addstring_data(struct Packet *, char *data, int len);
-static void ssh_pkt_addstring(struct Packet *, char *data);
+static void ssh_pkt_addstring_str(struct Packet *, const char *data);
+static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len);
+static void ssh_pkt_addstring(struct Packet *, const char *data);
 static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
 static void ssh1_pkt_addmp(struct Packet *, Bignum b);
 static void ssh2_pkt_addmp(struct Packet *, Bignum b);
@@ -517,10 +405,13 @@ static void ssh_channel_destroy(struct ssh_channel *c);
 #define OUR_V2_MAXPKT 0x4000UL
 #define OUR_V2_PACKETLIMIT 0x9000UL
 
-const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
+const static struct ssh_signkey *hostkey_algs[] = {
+    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
+    &ssh_rsa, &ssh_dss
+};
 
 const static struct ssh_mac *macs[] = {
-    &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+    &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
 const static struct ssh_mac *buggymacs[] = {
     &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
@@ -559,6 +450,14 @@ enum {                                    /* channel types */
     CHAN_AGENT,
     CHAN_SOCKDATA,
     CHAN_SOCKDATA_DORMANT,            /* one the remote hasn't confirmed */
+    /*
+     * CHAN_SHARING indicates a channel which is tracked here on
+     * behalf of a connection-sharing downstream. We do almost nothing
+     * with these channels ourselves: all messages relating to them
+     * get thrown straight to sshshare.c and passed on almost
+     * unmodified to downstream.
+     */
+    CHAN_SHARING,
     /*
      * CHAN_ZOMBIE is used to indicate a channel for which we've
      * already destroyed the local data source: for instance, if a
@@ -569,12 +468,18 @@ enum {                                   /* channel types */
     CHAN_ZOMBIE
 };
 
+typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
+typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
+typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *);
+
 /*
- * little structure to keep track of outstanding WINDOW_ADJUSTs
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
  */
-struct winadj {
-    struct winadj *next;
-    unsigned size;
+struct outstanding_channel_request {
+    cchandler_fn_t handler;
+    void *ctx;
+    struct outstanding_channel_request *next;
 };
 
 /*
@@ -643,10 +548,10 @@ struct ssh_channel {
             */
            int remlocwin;
            /*
-            * These store the list of window adjusts that haven't
+            * These store the list of channel requests that haven't
             * been acked.
             */
-           struct winadj *winadj_head, *winadj_tail;
+           struct outstanding_channel_request *chanreq_head, *chanreq_tail;
            enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
        } v2;
     } v;
@@ -655,13 +560,18 @@ struct ssh_channel {
            unsigned char *message;
            unsigned char msglen[4];
            unsigned lensofar, totallen;
+            int outstanding_requests;
        } a;
        struct ssh_x11_channel {
-           Socket s;
+           struct X11Connection *xconn;
+            int initial;
        } x11;
        struct ssh_pfd_channel {
-           Socket s;
+            struct PortForwarding *pf;
        } pfd;
+       struct ssh_sharing_channel {
+           void *ctx;
+       } sharing;
     } u;
 };
 
@@ -696,12 +606,21 @@ struct ssh_portfwd; /* forward declaration */
 
 struct ssh_rportfwd {
     unsigned sport, dport;
-    char dhost[256];
+    char *shost, *dhost;
     char *sportdesc;
+    void *share_ctx;
     struct ssh_portfwd *pfrec;
 };
-#define free_rportfwd(pf) ( \
-    ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
+
+static void free_rportfwd(struct ssh_rportfwd *pf)
+{
+    if (pf) {
+        sfree(pf->sportdesc);
+        sfree(pf->shost);
+        sfree(pf->dhost);
+        sfree(pf);
+    }
+}
 
 /*
  * Separately to the rportfwd tree (which is for looking up port
@@ -718,37 +637,63 @@ struct ssh_portfwd {
     char *sserv, *dserv;
     struct ssh_rportfwd *remote;
     int addressfamily;
-    void *local;
+    struct PortListener *local;
 };
 #define free_portfwd(pf) ( \
     ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \
             sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
 
 struct Packet {
-    long length;           /* length of `data' actually used */
+    long length;           /* length of packet: see below */
     long forcepad;         /* SSH-2: force padding to at least this length */
     int type;              /* only used for incoming packets */
     unsigned long sequence; /* SSH-2 incoming sequence number */
     unsigned char *data;    /* allocated storage */
     unsigned char *body;    /* offset of payload within `data' */
-    long savedpos;         /* temporary index into `data' (for strings) */
+    long savedpos;         /* dual-purpose saved packet position: see below */
     long maxlen;           /* amount of storage allocated for `data' */
     long encrypted_len;            /* for SSH-2 total-size counting */
 
     /*
-     * State associated with packet logging
+     * A note on the 'length' and 'savedpos' fields above.
+     *
+     * Incoming packets are set up so that pkt->length is measured
+     * relative to pkt->body, which itself points to a few bytes after
+     * pkt->data (skipping some uninteresting header fields including
+     * the packet type code). The ssh_pkt_get* functions all expect
+     * this setup, and they also use pkt->savedpos to indicate how far
+     * through the packet being decoded they've got - and that, too,
+     * is an offset from pkt->body rather than pkt->data.
+     *
+     * During construction of an outgoing packet, however, pkt->length
+     * is measured relative to the base pointer pkt->data, and
+     * pkt->body is not really used for anything until the packet is
+     * ready for sending. In this mode, pkt->savedpos is reused as a
+     * temporary variable by the addstring functions, which write out
+     * a string length field and then keep going back and updating it
+     * as more data is appended to the subsequent string data field;
+     * pkt->savedpos stores the offset (again relative to pkt->data)
+     * of the start of the string data field.
      */
-    int logmode;
-    int nblanks;
-    struct logblank_t *blanks;
+
+    /* Extra metadata used in SSH packet logging mode, allowing us to
+     * log in the packet header line that the packet came from a
+     * connection-sharing downstream and what if anything unusual was
+     * done to it. The additional_log_text field is expected to be a
+     * static string - it will not be freed. */
+    unsigned downstream_id;
+    const char *additional_log_text;
 };
 
 static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
                          struct Packet *pktin);
 static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
                          struct Packet *pktin);
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+                                          struct Packet *pktin);
 static void ssh1_protocol_setup(Ssh ssh);
 static void ssh2_protocol_setup(Ssh ssh);
+static void ssh2_bare_connection_protocol_setup(Ssh ssh);
 static void ssh_size(void *handle, int width, int height);
 static void ssh_special(void *handle, Telnet_Special);
 static int ssh2_try_send(struct ssh_channel *c);
@@ -760,9 +705,10 @@ static int ssh_do_close(Ssh ssh, int notify_exit);
 static unsigned long ssh_pkt_getuint32(struct Packet *pkt);
 static int ssh2_pkt_getbool(struct Packet *pkt);
 static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
-static void ssh2_timer(void *ctx, long now);
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
-                            struct Packet *pktin);
+static void ssh2_timer(void *ctx, unsigned long now);
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+                             struct Packet *pktin);
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
 
 struct rdpkt1_state_tag {
     long len, pad, biglen, to_read;
@@ -781,8 +727,13 @@ struct rdpkt2_state_tag {
     struct Packet *pktin;
 };
 
-typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
-typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
+struct rdpkt2_bare_state_tag {
+    char length[4];
+    long packetlen;
+    int i;
+    unsigned long incoming_sequence;
+    struct Packet *pktin;
+};
 
 struct queued_handler;
 struct queued_handler {
@@ -817,15 +768,21 @@ struct ssh_tag {
     const struct ssh2_cipher *cscipher, *sccipher;
     void *cs_cipher_ctx, *sc_cipher_ctx;
     const struct ssh_mac *csmac, *scmac;
+    int csmac_etm, scmac_etm;
     void *cs_mac_ctx, *sc_mac_ctx;
     const struct ssh_compress *cscomp, *sccomp;
     void *cs_comp_ctx, *sc_comp_ctx;
     const struct ssh_kex *kex;
     const struct ssh_signkey *hostkey;
+    char *hostkey_str; /* string representation, for easy checking in rekeys */
     unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
     int v2_session_id_len;
     void *kex_ctx;
 
+    int bare_connection;
+    int attempting_connshare;
+    void *connshare;
+
     char *savedhost;
     int savedport;
     int send_ok;
@@ -877,6 +834,8 @@ struct ssh_tag {
     Pkt_ACtx pkt_actx;
 
     struct X11Display *x11disp;
+    struct X11FakeAuth *x11auth;
+    tree234 *x11authtree;
 
     int version;
     int conn_throttle_count;
@@ -887,20 +846,19 @@ struct ssh_tag {
 
     int ssh1_rdpkt_crstate;
     int ssh2_rdpkt_crstate;
-    int do_ssh_init_crstate;
+    int ssh2_bare_rdpkt_crstate;
     int ssh_gotdata_crstate;
-    int do_ssh1_login_crstate;
     int do_ssh1_connection_crstate;
-    int do_ssh2_transport_crstate;
-    int do_ssh2_authconn_crstate;
 
     void *do_ssh_init_state;
     void *do_ssh1_login_state;
     void *do_ssh2_transport_state;
     void *do_ssh2_authconn_state;
+    void *do_ssh_connection_init_state;
 
     struct rdpkt1_state_tag rdpkt1_state;
     struct rdpkt2_state_tag rdpkt2_state;
+    struct rdpkt2_bare_state_tag rdpkt2_bare_state;
 
     /* SSH-1 and SSH-2 use this for different things, but both use it */
     int protocol_initial_phase_done;
@@ -908,6 +866,7 @@ struct ssh_tag {
     void (*protocol) (Ssh ssh, void *vin, int inlen,
                      struct Packet *pkt);
     struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
+    int (*do_ssh_init)(Ssh ssh, unsigned char c);
 
     /*
      * We maintain our own copy of a Conf structure here. That way,
@@ -961,6 +920,7 @@ struct ssh_tag {
      * indications from a request.
      */
     struct queued_handler *qhead, *qtail;
+    handler_fn_t q_saved_handler1, q_saved_handler2;
 
     /*
      * This module deals with sending keepalives.
@@ -974,7 +934,7 @@ struct ssh_tag {
     unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
     unsigned long max_data_size;
     int kex_in_progress;
-    long next_rekey, last_rekey;
+    unsigned long next_rekey, last_rekey;
     char *deferred_rekey_reason;    /* points to STATIC string; don't free */
 
     /*
@@ -1005,33 +965,15 @@ static void logeventf(Ssh ssh, const char *fmt, ...)
     sfree(buf);
 }
 
-#define bombout(msg) \
-    do { \
-        char *text = dupprintf msg; \
-       ssh_do_close(ssh, FALSE); \
-        logevent(text); \
-        connection_fatal(ssh->frontend, "%s", text); \
-        sfree(text); \
-    } while (0)
-
-/* Functions to leave bits out of the SSH packet log file. */
-
-static void dont_log_password(Ssh ssh, struct Packet *pkt, int blanktype)
-{
-    if (conf_get_int(ssh->conf, CONF_logomitpass))
-       pkt->logmode = blanktype;
-}
-
-static void dont_log_data(Ssh ssh, struct Packet *pkt, int blanktype)
+static void bomb_out(Ssh ssh, char *text)
 {
-    if (ssh->logomitdata)
-       pkt->logmode = blanktype;
+    ssh_do_close(ssh, FALSE);
+    logevent(text);
+    connection_fatal(ssh->frontend, "%s", text);
+    sfree(text);
 }
 
-static void end_log_omission(Ssh ssh, struct Packet *pkt)
-{
-    pkt->logmode = PKTLOG_EMIT;
-}
+#define bombout(msg) bomb_out(ssh, dupprintf msg)
 
 /* Helper function for common bits of parsing ttymodes. */
 static void parse_ttymodes(Ssh ssh,
@@ -1048,12 +990,14 @@ static void parse_ttymodes(Ssh ssh,
         * follows it, or 'A' indicating that we should pass the
         * value through from the local environment via get_ttymode.
         */
-       if (val[0] == 'A')
+       if (val[0] == 'A') {
            val = get_ttymode(ssh->frontend, key);
-       else
-           val++;                     /* skip the 'V' */
-       if (val)
-           do_mode(data, key, val);
+           if (val) {
+               do_mode(data, key, val);
+               sfree(val);
+           }
+       } else
+           do_mode(data, key, val + 1);               /* skip the 'V' */
     }
 }
 
@@ -1096,7 +1040,9 @@ static int ssh_rportcmp_ssh2(void *av, void *bv)
 {
     struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
     struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
-
+    int i;
+    if ( (i = strcmp(a->shost, b->shost)) != 0)
+       return i < 0 ? -1 : +1;
     if (a->sport > b->sport)
        return +1;
     if (a->sport < b->sport)
@@ -1227,13 +1173,119 @@ static struct Packet *ssh_new_packet(void)
 
     pkt->body = pkt->data = NULL;
     pkt->maxlen = 0;
-    pkt->logmode = PKTLOG_EMIT;
-    pkt->nblanks = 0;
-    pkt->blanks = NULL;
 
     return pkt;
 }
 
+static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt)
+{
+    int nblanks = 0;
+    struct logblank_t blanks[4];
+    char *str;
+    int slen;
+
+    pkt->savedpos = 0;
+
+    if (ssh->logomitdata &&
+        (pkt->type == SSH1_SMSG_STDOUT_DATA ||
+         pkt->type == SSH1_SMSG_STDERR_DATA ||
+         pkt->type == SSH1_MSG_CHANNEL_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        if (pkt->type == SSH1_MSG_CHANNEL_DATA)
+            ssh_pkt_getuint32(pkt);    /* skip channel id */
+        blanks[nblanks].offset = pkt->savedpos + 4;
+        blanks[nblanks].type = PKTLOG_OMIT;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (str) {
+            blanks[nblanks].len = slen;
+            nblanks++;
+        }
+    }
+    log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
+               ssh1_pkt_type(pkt->type),
+               pkt->body, pkt->length, nblanks, blanks, NULL,
+               0, NULL);
+}
+
+static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
+{
+    int nblanks = 0;
+    struct logblank_t blanks[4];
+    char *str;
+    int slen;
+
+    /*
+     * For outgoing packets, pkt->length represents the length of the
+     * whole packet starting at pkt->data (including some header), and
+     * pkt->body refers to the point within that where the log-worthy
+     * payload begins. However, incoming packets expect pkt->length to
+     * represent only the payload length (that is, it's measured from
+     * pkt->body not from pkt->data). Temporarily adjust our outgoing
+     * packet to conform to the incoming-packet semantics, so that we
+     * can analyse it with the ssh_pkt_get functions.
+     */
+    pkt->length -= (pkt->body - pkt->data);
+    pkt->savedpos = 0;
+
+    if (ssh->logomitdata &&
+        (pkt->type == SSH1_CMSG_STDIN_DATA ||
+         pkt->type == SSH1_MSG_CHANNEL_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        if (pkt->type == SSH1_MSG_CHANNEL_DATA)
+            ssh_pkt_getuint32(pkt);    /* skip channel id */
+        blanks[nblanks].offset = pkt->savedpos + 4;
+        blanks[nblanks].type = PKTLOG_OMIT;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (str) {
+            blanks[nblanks].len = slen;
+            nblanks++;
+        }
+    }
+
+    if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD ||
+         pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE ||
+         pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) &&
+        conf_get_int(ssh->conf, CONF_logomitpass)) {
+        /* If this is a password or similar packet, blank the password(s). */
+        blanks[nblanks].offset = 0;
+        blanks[nblanks].len = pkt->length;
+        blanks[nblanks].type = PKTLOG_BLANK;
+        nblanks++;
+    } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING &&
+               conf_get_int(ssh->conf, CONF_logomitpass)) {
+        /*
+         * If this is an X forwarding request packet, blank the fake
+         * auth data.
+         *
+         * 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.
+         */
+        pkt->savedpos = 0;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        blanks[nblanks].offset = pkt->savedpos;
+        blanks[nblanks].type = PKTLOG_BLANK;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (str) {
+            blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+            nblanks++;
+        }
+    }
+
+    log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
+               ssh1_pkt_type(pkt->data[12]),
+               pkt->body, pkt->length,
+               nblanks, blanks, NULL, 0, NULL);
+
+    /*
+     * Undo the above adjustment of pkt->length, to put the packet
+     * back in the state we found it.
+     */
+    pkt->length += (pkt->body - pkt->data);
+}
+
 /*
  * Collect incoming data in the incoming packet buffer.
  * Decipher and verify the packet when it is completely read.
@@ -1307,7 +1359,6 @@ static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     }
 
     st->pktin->body = st->pktin->data + st->pad + 1;
-    st->pktin->savedpos = 0;
 
     if (ssh->v1_compressing) {
        unsigned char *decompblk;
@@ -1336,37 +1387,167 @@ static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     st->pktin->type = st->pktin->body[-1];
 
     /*
-     * Log incoming packet, possibly omitting sensitive fields.
+     * Now pktin->body and pktin->length identify the semantic content
+     * of the packet, excluding the initial type byte.
      */
-    if (ssh->logctx) {
-       int nblanks = 0;
-       struct logblank_t blank;
-       if (ssh->logomitdata) {
-           int do_blank = FALSE, blank_prefix = 0;
-           /* "Session data" packets - omit the data field */
-           if ((st->pktin->type == SSH1_SMSG_STDOUT_DATA) ||
-               (st->pktin->type == SSH1_SMSG_STDERR_DATA)) {
-               do_blank = TRUE; blank_prefix = 4;
-           } else if (st->pktin->type == SSH1_MSG_CHANNEL_DATA) {
-               do_blank = TRUE; blank_prefix = 8;
-           }
-           if (do_blank) {
-               blank.offset = blank_prefix;
-               blank.len = st->pktin->length;
-               blank.type = PKTLOG_OMIT;
-               nblanks = 1;
-           }
-       }
-       log_packet(ssh->logctx,
-                  PKT_INCOMING, st->pktin->type,
-                  ssh1_pkt_type(st->pktin->type),
-                  st->pktin->body, st->pktin->length,
-                  nblanks, &blank, NULL);
-    }
+
+    if (ssh->logctx)
+        ssh1_log_incoming_packet(ssh, st->pktin);
+
+    st->pktin->savedpos = 0;
 
     crFinish(st->pktin);
 }
 
+static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt)
+{
+    int nblanks = 0;
+    struct logblank_t blanks[4];
+    char *str;
+    int slen;
+
+    pkt->savedpos = 0;
+
+    if (ssh->logomitdata &&
+        (pkt->type == SSH2_MSG_CHANNEL_DATA ||
+         pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        ssh_pkt_getuint32(pkt);    /* skip channel id */
+        if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+            ssh_pkt_getuint32(pkt);    /* skip extended data type */
+        blanks[nblanks].offset = pkt->savedpos + 4;
+        blanks[nblanks].type = PKTLOG_OMIT;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (str) {
+            blanks[nblanks].len = slen;
+            nblanks++;
+        }
+    }
+
+    log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
+               ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type),
+               pkt->body, pkt->length, nblanks, blanks, &pkt->sequence,
+               0, NULL);
+}
+
+static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
+{
+    int nblanks = 0;
+    struct logblank_t blanks[4];
+    char *str;
+    int slen;
+
+    /*
+     * For outgoing packets, pkt->length represents the length of the
+     * whole packet starting at pkt->data (including some header), and
+     * pkt->body refers to the point within that where the log-worthy
+     * payload begins. However, incoming packets expect pkt->length to
+     * represent only the payload length (that is, it's measured from
+     * pkt->body not from pkt->data). Temporarily adjust our outgoing
+     * packet to conform to the incoming-packet semantics, so that we
+     * can analyse it with the ssh_pkt_get functions.
+     */
+    pkt->length -= (pkt->body - pkt->data);
+    pkt->savedpos = 0;
+
+    if (ssh->logomitdata &&
+        (pkt->type == SSH2_MSG_CHANNEL_DATA ||
+         pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+        /* "Session data" packets - omit the data string. */
+        ssh_pkt_getuint32(pkt);    /* skip channel id */
+        if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+            ssh_pkt_getuint32(pkt);    /* skip extended data type */
+        blanks[nblanks].offset = pkt->savedpos + 4;
+        blanks[nblanks].type = PKTLOG_OMIT;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (str) {
+            blanks[nblanks].len = slen;
+            nblanks++;
+        }
+    }
+
+    if (pkt->type == SSH2_MSG_USERAUTH_REQUEST &&
+        conf_get_int(ssh->conf, CONF_logomitpass)) {
+        /* If this is a password packet, blank the password(s). */
+        pkt->savedpos = 0;
+        ssh_pkt_getstring(pkt, &str, &slen);
+        ssh_pkt_getstring(pkt, &str, &slen);
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (slen == 8 && !memcmp(str, "password", 8)) {
+            ssh2_pkt_getbool(pkt);
+            /* Blank the password field. */
+            blanks[nblanks].offset = pkt->savedpos;
+            blanks[nblanks].type = PKTLOG_BLANK;
+            ssh_pkt_getstring(pkt, &str, &slen);
+            if (str) {
+                blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+                nblanks++;
+                /* If there's another password field beyond it (change of
+                 * password), blank that too. */
+                ssh_pkt_getstring(pkt, &str, &slen);
+                if (str)
+                    blanks[nblanks-1].len =
+                        pkt->savedpos - blanks[nblanks].offset;
+            }
+        }
+    } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER &&
+               pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE &&
+               conf_get_int(ssh->conf, CONF_logomitpass)) {
+        /* If this is a keyboard-interactive response packet, blank
+         * the responses. */
+        pkt->savedpos = 0;
+        ssh_pkt_getuint32(pkt);
+        blanks[nblanks].offset = pkt->savedpos;
+        blanks[nblanks].type = PKTLOG_BLANK;
+        while (1) {
+            ssh_pkt_getstring(pkt, &str, &slen);
+            if (!str)
+                break;
+        }
+        blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+        nblanks++;
+    } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST &&
+               conf_get_int(ssh->conf, CONF_logomitpass)) {
+        /*
+         * If this is an X forwarding request packet, blank the fake
+         * auth data.
+         *
+         * 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.
+         */
+        pkt->savedpos = 0;
+        ssh_pkt_getuint32(pkt);
+        ssh_pkt_getstring(pkt, &str, &slen);
+        if (slen == 7 && !memcmp(str, "x11-req", 0)) {
+            ssh2_pkt_getbool(pkt);
+            ssh2_pkt_getbool(pkt);
+            ssh_pkt_getstring(pkt, &str, &slen);
+            blanks[nblanks].offset = pkt->savedpos;
+            blanks[nblanks].type = PKTLOG_BLANK;
+            ssh_pkt_getstring(pkt, &str, &slen);
+            if (str) {
+                blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+                nblanks++;
+            }
+        }
+    }
+
+    log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
+               ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]),
+               pkt->body, pkt->length, nblanks, blanks,
+               &ssh->v2_outgoing_sequence,
+               pkt->downstream_id, pkt->additional_log_text);
+
+    /*
+     * Undo the above adjustment of pkt->length, to put the packet
+     * back in the state we found it.
+     */
+    pkt->length += (pkt->body - pkt->data);
+}
+
 static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
 {
     struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
@@ -1386,7 +1567,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     st->maclen = ssh->scmac ? ssh->scmac->len : 0;
 
     if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
-       ssh->scmac) {
+       ssh->scmac && !ssh->scmac_etm) {
        /*
         * When dealing with a CBC-mode cipher, we want to avoid the
         * possibility of an attacker's tweaking the ciphertext stream
@@ -1398,6 +1579,11 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
         * length, so we just read data and check the MAC repeatedly,
         * and when the MAC passes, see if the length we've got is
         * plausible.
+         *
+         * This defence is unnecessary in OpenSSH ETM mode, because
+         * the whole point of ETM mode is that the attacker can't
+         * tweak the ciphertext stream at all without the MAC
+         * detecting it before we decrypt anything.
         */
 
        /* May as well allocate the whole lot now. */
@@ -1439,7 +1625,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
            /* See if that gives us a valid packet. */
            if (ssh->scmac->verresult(ssh->sc_mac_ctx,
                                      st->pktin->data + st->packetlen) &&
-               (st->len = GET_32BIT(st->pktin->data)) + 4 == st->packetlen)
+               ((st->len = toint(GET_32BIT(st->pktin->data))) ==
+                 st->packetlen-4))
                    break;
            if (st->packetlen >= OUR_V2_PACKETLIMIT) {
                bombout(("No valid incoming packet found"));
@@ -1451,6 +1638,71 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        st->pktin->data = sresize(st->pktin->data,
                                  st->pktin->maxlen + APIEXTRA,
                                  unsigned char);
+    } else if (ssh->scmac && ssh->scmac_etm) {
+       st->pktin->data = snewn(4 + APIEXTRA, unsigned char);
+
+        /*
+         * OpenSSH encrypt-then-MAC mode: the packet length is
+         * unencrypted.
+         */
+       for (st->i = st->len = 0; st->i < 4; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+       st->len = toint(GET_32BIT(st->pktin->data));
+
+       /*
+        * _Completely_ silly lengths should be stomped on before they
+        * do us any more damage.
+        */
+       if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT ||
+           st->len % st->cipherblk != 0) {
+           bombout(("Incoming packet length field was garbled"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /*
+        * So now we can work out the total packet length.
+        */
+       st->packetlen = st->len + 4;
+
+       /*
+        * Allocate memory for the rest of the packet.
+        */
+       st->pktin->maxlen = st->packetlen + st->maclen;
+       st->pktin->data = sresize(st->pktin->data,
+                                 st->pktin->maxlen + APIEXTRA,
+                                 unsigned char);
+
+       /*
+        * Read the remainder of the packet.
+        */
+       for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+
+       /*
+        * Check the MAC.
+        */
+       if (ssh->scmac
+           && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data,
+                                  st->len + 4, st->incoming_sequence)) {
+           bombout(("Incorrect MAC received on packet"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /* Decrypt everything between the length field and the MAC. */
+       if (ssh->sccipher)
+           ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+                                  st->pktin->data + 4,
+                                  st->packetlen - 4);
     } else {
        st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
 
@@ -1472,7 +1724,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        /*
         * Now get the length figure.
         */
-       st->len = GET_32BIT(st->pktin->data);
+       st->len = toint(GET_32BIT(st->pktin->data));
 
        /*
         * _Completely_ silly lengths should be stomped on before they
@@ -1542,6 +1794,9 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
 
     st->pktin->sequence = st->incoming_sequence++;
 
+    st->pktin->length = st->packetlen - st->pad;
+    assert(st->pktin->length >= 0);
+
     /*
      * Decompress packet payload.
      */
@@ -1564,38 +1819,79 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        }
     }
 
-    st->pktin->savedpos = 6;
-    st->pktin->body = st->pktin->data;
+    /*
+     * pktin->body and pktin->length should identify the semantic
+     * content of the packet, excluding the initial type byte.
+     */
     st->pktin->type = st->pktin->data[5];
+    st->pktin->body = st->pktin->data + 6;
+    st->pktin->length -= 6;
+    assert(st->pktin->length >= 0);    /* one last double-check */
+
+    if (ssh->logctx)
+        ssh2_log_incoming_packet(ssh, st->pktin);
+
+    st->pktin->savedpos = 0;
+
+    crFinish(st->pktin);
+}
+
+static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data,
+                                                 int *datalen)
+{
+    struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
+
+    crBegin(ssh->ssh2_bare_rdpkt_crstate);
 
     /*
-     * Log incoming packet, possibly omitting sensitive fields.
+     * Read the packet length field.
      */
-    if (ssh->logctx) {
-       int nblanks = 0;
-       struct logblank_t blank;
-       if (ssh->logomitdata) {
-           int do_blank = FALSE, blank_prefix = 0;
-           /* "Session data" packets - omit the data field */
-           if (st->pktin->type == SSH2_MSG_CHANNEL_DATA) {
-               do_blank = TRUE; blank_prefix = 8;
-           } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
-               do_blank = TRUE; blank_prefix = 12;
-           }
-           if (do_blank) {
-               blank.offset = blank_prefix;
-               blank.len = (st->pktin->length-6) - blank_prefix;
-               blank.type = PKTLOG_OMIT;
-               nblanks = 1;
-           }
-       }
-       log_packet(ssh->logctx, PKT_INCOMING, st->pktin->type,
-                  ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
-                                st->pktin->type),
-                  st->pktin->data+6, st->pktin->length-6,
-                  nblanks, &blank, &st->pktin->sequence);
+    for (st->i = 0; st->i < 4; st->i++) {
+        while ((*datalen) == 0)
+            crReturn(NULL);
+        st->length[st->i] = *(*data)++;
+        (*datalen)--;
+    }
+
+    st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length));
+    if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) {
+        bombout(("Invalid packet length received"));
+        crStop(NULL);
+    }
+
+    st->pktin = ssh_new_packet();
+    st->pktin->data = snewn(st->packetlen, unsigned char);
+
+    st->pktin->encrypted_len = st->packetlen;
+
+    st->pktin->sequence = st->incoming_sequence++;
+
+    /*
+     * Read the remainder of the packet.
+     */
+    for (st->i = 0; st->i < st->packetlen; st->i++) {
+        while ((*datalen) == 0)
+            crReturn(NULL);
+        st->pktin->data[st->i] = *(*data)++;
+        (*datalen)--;
     }
 
+    /*
+     * pktin->body and pktin->length should identify the semantic
+     * content of the packet, excluding the initial type byte.
+     */
+    st->pktin->type = st->pktin->data[0];
+    st->pktin->body = st->pktin->data + 1;
+    st->pktin->length = st->packetlen - 1;
+
+    /*
+     * Log incoming packet, possibly omitting sensitive fields.
+     */
+    if (ssh->logctx)
+        ssh2_log_incoming_packet(ssh, st->pktin);
+
+    st->pktin->savedpos = 0;
+
     crFinish(st->pktin);
 }
 
@@ -1615,12 +1911,7 @@ static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
     int len;
 
     if (ssh->logctx)
-       log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
-                  ssh1_pkt_type(pkt->data[12]),
-                  pkt->body, pkt->length - (pkt->body - pkt->data),
-                  pkt->nblanks, pkt->blanks, NULL);
-    sfree(pkt->blanks); pkt->blanks = NULL;
-    pkt->nblanks = 0;
+        ssh1_log_outgoing_packet(ssh, pkt);
 
     if (ssh->v1_compressing) {
        unsigned char *compblk;
@@ -1659,7 +1950,9 @@ static int s_write(Ssh ssh, void *data, int len)
 {
     if (ssh->logctx)
        log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len,
-                  0, NULL, NULL);
+                  0, NULL, NULL, 0, NULL);
+    if (!ssh->s)
+        return 0;
     return sk_write(ssh->s, (char *)data, len);
 }
 
@@ -1730,16 +2023,6 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
            bn = va_arg(ap, Bignum);
            ssh1_pkt_addmp(pkt, bn);
            break;
-         /* Tokens for modifications to packet logging */
-         case PKTT_PASSWORD:
-           dont_log_password(ssh, pkt, PKTLOG_BLANK);
-           break;
-         case PKTT_DATA:
-           dont_log_data(ssh, pkt, PKTLOG_OMIT);
-           break;
-         case PKTT_OTHER:
-           end_log_omission(ssh, pkt);
-           break;
        }
     }
 
@@ -1818,17 +2101,8 @@ static void ssh_pkt_ensure(struct Packet *pkt, int length)
        if (body) pkt->body = pkt->data + offset;
     }
 }
-static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len)
+static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len)
 {
-    if (pkt->logmode != PKTLOG_EMIT) {
-       pkt->nblanks++;
-       pkt->blanks = sresize(pkt->blanks, pkt->nblanks, struct logblank_t);
-       assert(pkt->body);
-       pkt->blanks[pkt->nblanks-1].offset = pkt->length -
-                                            (pkt->body - pkt->data);
-       pkt->blanks[pkt->nblanks-1].len = len;
-       pkt->blanks[pkt->nblanks-1].type = pkt->logmode;
-    }
     pkt->length += len;
     ssh_pkt_ensure(pkt, pkt->length);
     memcpy(pkt->data + pkt->length - len, data, len);
@@ -1852,17 +2126,18 @@ static void ssh_pkt_addstring_start(struct Packet *pkt)
     ssh_pkt_adduint32(pkt, 0);
     pkt->savedpos = pkt->length;
 }
-static void ssh_pkt_addstring_str(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
 {
     ssh_pkt_adddata(pkt, data, strlen(data));
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh_pkt_addstring_data(struct Packet *pkt, char *data, int len)
+static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data,
+                                   int len)
 {
     ssh_pkt_adddata(pkt, data, len);
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh_pkt_addstring(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring(struct Packet *pkt, const char *data)
 {
     ssh_pkt_addstring_start(pkt);
     ssh_pkt_addstring_str(pkt, data);
@@ -1906,6 +2181,9 @@ static struct Packet *ssh1_pkt_init(int pkt_type)
     pkt->length = 4 + 8;           /* space for length + max padding */
     ssh_pkt_addbyte(pkt, pkt_type);
     pkt->body = pkt->data + pkt->length;
+    pkt->type = pkt_type;
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
     return pkt;
 }
 
@@ -1924,8 +2202,11 @@ static struct Packet *ssh2_pkt_init(int pkt_type)
     struct Packet *pkt = ssh_new_packet();
     pkt->length = 5; /* space for packet length + padding length */
     pkt->forcepad = 0;
+    pkt->type = pkt_type;
     ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
     pkt->body = pkt->data + pkt->length; /* after packet type */
+    pkt->downstream_id = 0;
+    pkt->additional_log_text = NULL;
     return pkt;
 }
 
@@ -1936,15 +2217,21 @@ static struct Packet *ssh2_pkt_init(int pkt_type)
  */
 static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
 {
-    int cipherblk, maclen, padding, i;
+    int cipherblk, maclen, padding, unencrypted_prefix, i;
 
     if (ssh->logctx)
-       log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
-                  ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]),
-                  pkt->body, pkt->length - (pkt->body - pkt->data),
-                  pkt->nblanks, pkt->blanks, &ssh->v2_outgoing_sequence);
-    sfree(pkt->blanks); pkt->blanks = NULL;
-    pkt->nblanks = 0;
+        ssh2_log_outgoing_packet(ssh, pkt);
+
+    if (ssh->bare_connection) {
+        /*
+         * Trivial packet construction for the bare connection
+         * protocol.
+         */
+        PUT_32BIT(pkt->data + 1, pkt->length - 5);
+        pkt->body = pkt->data + 1;
+        ssh->v2_outgoing_sequence++;   /* only for diagnostics, really */
+        return pkt->length - 1;
+    }
 
     /*
      * Compress packet payload.
@@ -1971,10 +2258,12 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
     cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
     padding = 4;
+    unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0;
     if (pkt->length + padding < pkt->forcepad)
        padding = pkt->forcepad - pkt->length;
     padding +=
-       (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+       (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+        % cipherblk;
     assert(padding <= 255);
     maclen = ssh->csmac ? ssh->csmac->len : 0;
     ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
@@ -1982,19 +2271,34 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     for (i = 0; i < padding; i++)
        pkt->data[pkt->length + i] = random_byte();
     PUT_32BIT(pkt->data, pkt->length + padding - 4);
-    if (ssh->csmac)
-       ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
-                            pkt->length + padding,
-                            ssh->v2_outgoing_sequence);
-    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
-
-    if (ssh->cscipher)
-       ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
-                              pkt->data, pkt->length + padding);
+    if (ssh->csmac && ssh->csmac_etm) {
+        /*
+         * OpenSSH-defined encrypt-then-MAC protocol.
+         */
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data + 4, pkt->length + padding - 4);
+        ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                             pkt->length + padding,
+                             ssh->v2_outgoing_sequence);
+    } else {
+        /*
+         * SSH-2 standard protocol.
+         */
+        if (ssh->csmac)
+            ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                                 pkt->length + padding,
+                                 ssh->v2_outgoing_sequence);
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data, pkt->length + padding);
+    }
 
+    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
     pkt->encrypted_len = pkt->length + padding;
 
     /* Ready-to-send packet starts at pkt->data. We return length. */
+    pkt->body = pkt->data;
     return pkt->length + padding + maclen;
 }
 
@@ -2052,12 +2356,13 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
        return;
     }
     len = ssh2_pkt_construct(ssh, pkt);
-    backlog = s_write(ssh, pkt->data, len);
+    backlog = s_write(ssh, pkt->body, len);
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
 
     ssh->outgoing_data_size += pkt->encrypted_len;
     if (!ssh->kex_in_progress &&
+        !ssh->bare_connection &&
        ssh->max_data_size != 0 &&
        ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "too much data sent", -1, NULL);
@@ -2089,7 +2394,7 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
                                          ssh->deferred_size,
                                          unsigned char);
     }
-    memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len);
+    memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len);
     ssh->deferred_len += len;
     ssh->deferred_data_size += pkt->encrypted_len;
     ssh_free_packet(pkt);
@@ -2159,6 +2464,7 @@ static void ssh_pkt_defersend(Ssh ssh)
 
     ssh->outgoing_data_size += ssh->deferred_data_size;
     if (!ssh->kex_in_progress &&
+        !ssh->bare_connection &&
        ssh->max_data_size != 0 &&
        ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "too much data sent", -1, NULL);
@@ -2303,7 +2609,7 @@ static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length)
     *length = 0;
     if (pkt->length - pkt->savedpos < 4)
        return;
-    len = GET_32BIT(pkt->body + pkt->savedpos);
+    len = toint(GET_32BIT(pkt->body + pkt->savedpos));
     if (len < 0)
        return;
     *length = len;
@@ -2387,7 +2693,7 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
      * 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) &&
+    if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 &&
        (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
        int pos, len, siglen;
 
@@ -2396,8 +2702,15 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
         */
 
        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 */
+        len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */
+        if (len < 0 || len > pkblob_len - pos - 4)
+            goto give_up;
+       pos += 4 + len;                /* skip over exponent */
+        if (pkblob_len - pos < 4)
+            goto give_up;
+       len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */
+        if (len < 0 || len > pkblob_len - pos - 4)
+            goto give_up;
        pos += 4;                      /* find modulus itself */
        while (len > 0 && pkblob[pos] == 0)
            len--, pos++;
@@ -2407,7 +2720,11 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
         * Now find the signature integer.
         */
        pos = 4+7;                     /* skip over "ssh-rsa" */
-       siglen = GET_32BIT(sigblob+pos);
+        if (sigblob_len < pos+4)
+            goto give_up;
+       siglen = toint(GET_32BIT(sigblob+pos));
+        if (siglen != sigblob_len - pos - 4)
+            goto give_up;
        /* debug(("signature length is %d\n", siglen)); */
 
        if (len != siglen) {
@@ -2429,7 +2746,10 @@ static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
            return;
        }
 
-       /* Otherwise fall through and do it the easy way. */
+       /* Otherwise fall through and do it the easy way. We also come
+         * here as a fallback if we discover above that the key blob
+         * is misformatted in some way. */
+      give_up:;
     }
 
     ssh2_pkt_addstring_start(pkt);
@@ -2526,7 +2846,9 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
     if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
        (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO &&
         (wc_match("OpenSSH_2.[5-9]*", imp) ||
-         wc_match("OpenSSH_3.[0-2]*", imp)))) {
+         wc_match("OpenSSH_3.[0-2]*", imp) ||
+         wc_match("mod_sftp/0.[0-8]*", imp) ||
+         wc_match("mod_sftp/0.9.[0-8]", imp)))) {
        /*
         * These versions have the SSH-2 RSA padding bug.
         */
@@ -2580,6 +2902,43 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
        ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
        logevent("We believe remote version has SSH-2 ignore bug");
     }
+
+    if (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == FORCE_ON ||
+       (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == AUTO &&
+        (wc_match("OpenSSH_2.[235]*", imp)))) {
+       /*
+        * These versions only support the original (pre-RFC4419)
+        * SSH-2 GEX request.
+        */
+       ssh->remote_bugs |= BUG_SSH2_OLDGEX;
+       logevent("We believe remote version has outdated SSH-2 GEX");
+    }
+
+    if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) {
+       /*
+        * Servers that don't support our winadj request for one
+        * reason or another. Currently, none detected automatically.
+        */
+       ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+       logevent("We believe remote version has winadj bug");
+    }
+
+    if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON ||
+       (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == AUTO &&
+        (wc_match("OpenSSH_[2-5].*", imp) ||
+         wc_match("OpenSSH_6.[0-6]*", imp) ||
+         wc_match("dropbear_0.[2-4][0-9]*", imp) ||
+         wc_match("dropbear_0.5[01]*", imp)))) {
+       /*
+        * These versions have the SSH-2 channel request bug.
+        * OpenSSH 6.7 and above do not:
+        * https://bugzilla.mindrot.org/show_bug.cgi?id=1818
+        * dropbear_0.52 and above do not:
+        * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c
+        */
+       ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
+       logevent("We believe remote version has SSH-2 channel request bug");
+    }
 }
 
 /*
@@ -2588,11 +2947,7 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
  */
 static void ssh_fix_verstring(char *str)
 {
-    /* Eat "SSH-<protoversion>-". */
-    assert(*str == 'S'); str++;
-    assert(*str == 'S'); str++;
-    assert(*str == 'H'); str++;
-    assert(*str == '-'); str++;
+    /* Eat "<protoversion>-". */
     while (*str && *str != '-') str++;
     assert(*str == '-'); str++;
 
@@ -2608,7 +2963,7 @@ static void ssh_fix_verstring(char *str)
 /*
  * Send an appropriate SSH version string.
  */
-static void ssh_send_verstring(Ssh ssh, char *svers)
+static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
 {
     char *verstring;
 
@@ -2616,18 +2971,19 @@ static void ssh_send_verstring(Ssh ssh, char *svers)
        /*
         * Construct a v2 version string.
         */
-       verstring = dupprintf("SSH-2.0-%s\015\012", sshver);
+       verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver);
     } else {
        /*
         * Construct a v1 version string.
         */
+        assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */
        verstring = dupprintf("SSH-%s-%s\012",
                              (ssh_versioncmp(svers, "1.5") <= 0 ?
                               svers : "1.5"),
                              sshver);
     }
 
-    ssh_fix_verstring(verstring);
+    ssh_fix_verstring(verstring + strlen(protoname));
 
     if (ssh->version == 2) {
        size_t len;
@@ -2648,7 +3004,10 @@ static void ssh_send_verstring(Ssh ssh, char *svers)
 
 static int do_ssh_init(Ssh ssh, unsigned char c)
 {
+    static const char protoname[] = "SSH-";
+
     struct do_ssh_init_state {
+       int crLine;
        int vslen;
        char version[10];
        char *vstring;
@@ -2657,18 +3016,16 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
        int proto1, proto2;
     };
     crState(do_ssh_init_state);
+    
+    crBeginState;
 
-    crBegin(ssh->do_ssh_init_crstate);
-
-    /* Search for a line beginning with the string "SSH-" in the input. */
+    /* Search for a line beginning with the protocol name prefix 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;
+        for (s->i = 0; protoname[s->i]; s->i++) {
+            if ((char)c != protoname[s->i]) goto no;
+            crReturn(1);
+        }
        break;
       no:
        while (c != '\012')
@@ -2676,13 +3033,12 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
        crReturn(1);
     }
 
-    s->vstrsize = 16;
+    s->vstrsize = sizeof(protoname) + 16;
     s->vstring = snewn(s->vstrsize, char);
-    strcpy(s->vstring, "SSH-");
-    s->vslen = 4;
+    strcpy(s->vstring, protoname);
+    s->vslen = strlen(protoname);
     s->i = 0;
     while (1) {
-       crReturn(1);                   /* get another char */
        if (s->vslen >= s->vstrsize - 1) {
            s->vstrsize += 16;
            s->vstring = sresize(s->vstring, s->vstrsize, char);
@@ -2696,6 +3052,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
                s->version[s->i++] = c;
        } else if (c == '\012')
            break;
+       crReturn(1);                   /* get another char */
     }
 
     ssh->agentfwd_enabled = FALSE;
@@ -2716,11 +3073,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
 
     if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
-       bombout(("SSH protocol version 1 required by user but not provided by server"));
+       bombout(("SSH protocol version 1 required by configuration but "
+                "not provided by server"));
        crStop(0);
     }
     if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
-       bombout(("SSH protocol version 2 required by user but not provided by server"));
+       bombout(("SSH protocol version 2 required by configuration but "
+                "not provided by server"));
        crStop(0);
     }
 
@@ -2733,7 +3092,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
 
     /* Send the version string, if we haven't already */
     if (conf_get_int(ssh->conf, CONF_sshprot) != 3)
-       ssh_send_verstring(ssh, s->version);
+       ssh_send_verstring(ssh, protoname, s->version);
 
     if (ssh->version == 2) {
        size_t len;
@@ -2771,21 +3130,132 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     crFinish(0);
 }
 
-static void ssh_process_incoming_data(Ssh ssh,
-                                     unsigned char **data, int *datalen)
+static int do_ssh_connection_init(Ssh ssh, unsigned char c)
 {
-    struct Packet *pktin;
+    /*
+     * Ordinary SSH begins with the banner "SSH-x.y-...". This is just
+     * the ssh-connection part, extracted and given a trivial binary
+     * packet protocol, so we replace 'SSH-' at the start with a new
+     * name. In proper SSH style (though of course this part of the
+     * proper SSH protocol _isn't_ subject to this kind of
+     * DNS-domain-based extension), we define the new name in our
+     * extension space.
+     */
+    static const char protoname[] =
+        "SSHCONNECTION@putty.projects.tartarus.org-";
 
-    pktin = ssh->s_rdpkt(ssh, data, datalen);
-    if (pktin) {
-       ssh->protocol(ssh, NULL, 0, pktin);
-       ssh_free_packet(pktin);
+    struct do_ssh_connection_init_state {
+       int crLine;
+       int vslen;
+       char version[10];
+       char *vstring;
+       int vstrsize;
+       int i;
+    };
+    crState(do_ssh_connection_init_state);
+    
+    crBeginState;
+
+    /* Search for a line beginning with the protocol name prefix in
+     * the input. */
+    for (;;) {
+        for (s->i = 0; protoname[s->i]; s->i++) {
+            if ((char)c != protoname[s->i]) goto no;
+            crReturn(1);
+        }
+       break;
+      no:
+       while (c != '\012')
+           crReturn(1);
+       crReturn(1);
     }
-}
 
-static void ssh_queue_incoming_data(Ssh ssh,
-                                   unsigned char **data, int *datalen)
-{
+    s->vstrsize = sizeof(protoname) + 16;
+    s->vstring = snewn(s->vstrsize, char);
+    strcpy(s->vstring, protoname);
+    s->vslen = strlen(protoname);
+    s->i = 0;
+    while (1) {
+       if (s->vslen >= s->vstrsize - 1) {
+           s->vstrsize += 16;
+           s->vstring = sresize(s->vstring, s->vstrsize, char);
+       }
+       s->vstring[s->vslen++] = c;
+       if (s->i >= 0) {
+           if (c == '-') {
+               s->version[s->i] = '\0';
+               s->i = -1;
+           } else if (s->i < sizeof(s->version) - 1)
+               s->version[s->i++] = c;
+       } else if (c == '\012')
+           break;
+       crReturn(1);                   /* get another char */
+    }
+
+    ssh->agentfwd_enabled = FALSE;
+    ssh->rdpkt2_bare_state.incoming_sequence = 0;
+
+    s->vstring[s->vslen] = 0;
+    s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
+    logeventf(ssh, "Server version: %s", s->vstring);
+    ssh_detect_bugs(ssh, s->vstring);
+
+    /*
+     * Decide which SSH protocol version to support. This is easy in
+     * bare ssh-connection mode: only 2.0 is legal.
+     */
+    if (ssh_versioncmp(s->version, "2.0") < 0) {
+       bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol"));
+        crStop(0);
+    }
+    if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+       bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode"));
+       crStop(0);
+    }
+
+    ssh->version = 2;
+
+    logeventf(ssh, "Using bare ssh-connection protocol");
+
+    /* Send the version string, if we haven't already */
+    ssh_send_verstring(ssh, protoname, s->version);
+
+    /*
+     * Initialise bare connection protocol.
+     */
+    ssh->protocol = ssh2_bare_connection_protocol;
+    ssh2_bare_connection_protocol_setup(ssh);
+    ssh->s_rdpkt = ssh2_bare_connection_rdpkt;
+
+    update_specials_menu(ssh->frontend);
+    ssh->state = SSH_STATE_BEFORE_SIZE;
+    ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh);
+
+    /*
+     * Get authconn (really just conn) under way.
+     */
+    do_ssh2_authconn(ssh, NULL, 0, NULL);
+
+    sfree(s->vstring);
+
+    crFinish(0);
+}
+
+static void ssh_process_incoming_data(Ssh ssh,
+                                     unsigned char **data, int *datalen)
+{
+    struct Packet *pktin;
+
+    pktin = ssh->s_rdpkt(ssh, data, datalen);
+    if (pktin) {
+       ssh->protocol(ssh, NULL, 0, pktin);
+       ssh_free_packet(pktin);
+    }
+}
+
+static void ssh_queue_incoming_data(Ssh ssh,
+                                   unsigned char **data, int *datalen)
+{
     bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
     *data += *datalen;
     *datalen = 0;
@@ -2822,7 +3292,7 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
     /* Log raw data, if we're in that mode. */
     if (ssh->logctx)
        log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen,
-                  0, NULL, NULL);
+                  0, NULL, NULL, 0, NULL);
 
     crBegin(ssh->ssh_gotdata_crstate);
 
@@ -2836,7 +3306,7 @@ static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
        int ret;                       /* need not be kept across crReturn */
        if (datalen == 0)
            crReturnV;                 /* more data please */
-       ret = do_ssh_init(ssh, *data);
+       ret = ssh->do_ssh_init(ssh, *data);
        data++;
        datalen--;
        if (ret == 0)
@@ -2900,11 +3370,11 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
        while (NULL != (c = index234(ssh->channels, 0))) {
            switch (c->type) {
              case CHAN_X11:
-               x11_close(c->u.x11.s);
+               x11_close(c->u.x11.xconn);
                break;
              case CHAN_SOCKDATA:
              case CHAN_SOCKDATA_DORMANT:
-               pfd_close(c->u.pfd.s);
+               pfd_close(c->u.pfd.pf);
                break;
            }
            del234(ssh->channels, c); /* moving next one to index 0 */
@@ -2922,7 +3392,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
        while (NULL != (pf = index234(ssh->portfwds, 0))) {
            /* Dispose of any listening socket. */
            if (pf->local)
-               pfd_terminate(pf->local);
+               pfl_terminate(pf->local);
            del234(ssh->portfwds, pf); /* moving next one to index 0 */
            free_portfwd(pf);
        }
@@ -2930,24 +3400,82 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
        ssh->portfwds = NULL;
     }
 
+    /*
+     * Also stop attempting to connection-share.
+     */
+    if (ssh->connshare) {
+        sharestate_free(ssh->connshare);
+        ssh->connshare = NULL;
+    }
+
     return ret;
 }
 
-static void ssh_log(Plug plug, int type, SockAddr addr, int port,
-                   const char *error_msg, int error_code)
+static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port,
+                           const char *error_msg, int error_code)
 {
     Ssh ssh = (Ssh) plug;
     char addrbuf[256], *msg;
 
-    sk_getaddr(addr, addrbuf, lenof(addrbuf));
+    if (ssh->attempting_connshare) {
+        /*
+         * While we're attempting connection sharing, don't loudly log
+         * everything that happens. Real TCP connections need to be
+         * logged when we _start_ trying to connect, because it might
+         * be ages before they respond if something goes wrong; but
+         * connection sharing is local and quick to respond, and it's
+         * sufficient to simply wait and see whether it worked
+         * afterwards.
+         */
+    } else {
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+
+        if (type == 0) {
+            if (sk_addr_needs_port(addr)) {
+                msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+            } else {
+                msg = dupprintf("Connecting to %s", addrbuf);
+            }
+        } else {
+            msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        }
 
-    if (type == 0)
-       msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-    else
-       msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        logevent(msg);
+        sfree(msg);
+    }
+}
 
-    logevent(msg);
-    sfree(msg);
+void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
+                       const char *ds_err, const char *us_err)
+{
+    if (event == SHARE_NONE) {
+        /* In this case, 'logtext' is an error message indicating a
+         * reason why connection sharing couldn't be set up _at all_.
+         * Failing that, ds_err and us_err indicate why we couldn't be
+         * a downstream and an upstream respectively. */
+        if (logtext) {
+            logeventf(ssh, "Could not set up connection sharing: %s", logtext);
+        } else {
+            if (ds_err)
+                logeventf(ssh, "Could not set up connection sharing"
+                          " as downstream: %s", ds_err);
+            if (us_err)
+                logeventf(ssh, "Could not set up connection sharing"
+                          " as upstream: %s", us_err);
+        }
+    } else if (event == SHARE_DOWNSTREAM) {
+        /* In this case, 'logtext' is a local endpoint address */
+        logeventf(ssh, "Using existing shared connection at %s", logtext);
+        /* Also we should mention this in the console window to avoid
+         * confusing users as to why this window doesn't behave the
+         * usual way. */
+        if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+            c_write_str(ssh,"Reusing a shared connection to this server.\r\n");
+        }
+    } else if (event == SHARE_UPSTREAM) {
+        /* In this case, 'logtext' is a local endpoint address too */
+        logeventf(ssh, "Sharing this connection at %s", logtext);
+    }
 }
 
 static int ssh_closing(Plug plug, const char *error_msg, int error_code,
@@ -3008,7 +3536,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
                                   char **realhost, int nodelay, int keepalive)
 {
     static const struct plug_function_table fn_table = {
-       ssh_log,
+       ssh_socket_log,
        ssh_closing,
        ssh_receive,
        ssh_sent,
@@ -3022,54 +3550,85 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     
     loghost = conf_get_str(ssh->conf, CONF_loghost);
     if (*loghost) {
-       char *colon;
+       char *tmphost;
+        char *colon;
 
-       ssh->savedhost = dupstr(loghost);
+        tmphost = dupstr(loghost);
        ssh->savedport = 22;           /* default ssh port */
 
        /*
-        * A colon suffix on savedhost also lets us affect
-        * savedport.
-        * 
-        * (FIXME: do something about IPv6 address literals here.)
+        * A colon suffix on the hostname string also lets us affect
+        * savedport. (Unless there are multiple colons, in which case
+        * we assume this is an unbracketed IPv6 literal.)
         */
-       colon = strrchr(ssh->savedhost, ':');
-       if (colon) {
+       colon = host_strrchr(tmphost, ':');
+       if (colon && colon == host_strchr(tmphost, ':')) {
            *colon++ = '\0';
            if (*colon)
                ssh->savedport = atoi(colon);
        }
+
+        ssh->savedhost = host_strduptrim(tmphost);
+        sfree(tmphost);
     } else {
-       ssh->savedhost = dupstr(host);
+       ssh->savedhost = host_strduptrim(host);
        if (port < 0)
            port = 22;                 /* default ssh port */
        ssh->savedport = port;
     }
 
-    /*
-     * Try to find host.
-     */
-    addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
-    logeventf(ssh, "Looking up host \"%s\"%s", host,
-             (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-              (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
-    addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
-    if ((err = sk_addr_error(addr)) != NULL) {
-       sk_addr_free(addr);
-       return err;
-    }
-    ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
+    ssh->fn = &fn_table;               /* make 'ssh' usable as a Plug */
 
     /*
-     * Open socket.
+     * Try connection-sharing, in case that means we don't open a
+     * socket after all. ssh_connection_sharing_init will connect to a
+     * previously established upstream if it can, and failing that,
+     * establish a listening socket for _us_ to be the upstream. In
+     * the latter case it will return NULL just as if it had done
+     * nothing, because here we only need to care if we're a
+     * downstream and need to do our connection setup differently.
      */
-    ssh->fn = &fn_table;
-    ssh->s = new_connection(addr, *realhost, port,
-                           0, 1, nodelay, keepalive, (Plug) ssh, ssh->conf);
-    if ((err = sk_socket_error(ssh->s)) != NULL) {
-       ssh->s = NULL;
-       notify_remote_exit(ssh->frontend);
-       return err;
+    ssh->connshare = NULL;
+    ssh->attempting_connshare = TRUE;  /* affects socket logging behaviour */
+    ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport,
+                                         ssh->conf, ssh, &ssh->connshare);
+    ssh->attempting_connshare = FALSE;
+    if (ssh->s != NULL) {
+        /*
+         * We are a downstream.
+         */
+        ssh->bare_connection = TRUE;
+        ssh->do_ssh_init = do_ssh_connection_init;
+        ssh->fullhostname = NULL;
+        *realhost = dupstr(host);      /* best we can do */
+    } else {
+        /*
+         * We're not a downstream, so open a normal socket.
+         */
+        ssh->do_ssh_init = do_ssh_init;
+
+        /*
+         * Try to find host.
+         */
+        addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
+        logeventf(ssh, "Looking up host \"%s\"%s", host,
+                  (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+                   (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
+        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+        if ((err = sk_addr_error(addr)) != NULL) {
+            sk_addr_free(addr);
+            return err;
+        }
+        ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
+
+        ssh->s = new_connection(addr, *realhost, port,
+                                0, 1, nodelay, keepalive,
+                                (Plug) ssh, ssh->conf);
+        if ((err = sk_socket_error(ssh->s)) != NULL) {
+            ssh->s = NULL;
+            notify_remote_exit(ssh->frontend);
+            return err;
+        }
     }
 
     /*
@@ -3079,9 +3638,9 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     sshprot = conf_get_int(ssh->conf, CONF_sshprot);
     if (sshprot == 0)
        ssh->version = 1;
-    if (sshprot == 3) {
+    if (sshprot == 3 && !ssh->bare_connection) {
        ssh->version = 2;
-       ssh_send_verstring(ssh, NULL);
+       ssh_send_verstring(ssh, "SSH-", NULL);
     }
 
     /*
@@ -3133,13 +3692,13 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
             */
            break;
          case CHAN_X11:
-           x11_override_throttle(c->u.x11.s, enable);
+           x11_override_throttle(c->u.x11.xconn, enable);
            break;
          case CHAN_AGENT:
            /* Agent channels require no buffer management. */
            break;
          case CHAN_SOCKDATA:
-           pfd_override_throttle(c->u.pfd.s, enable);
+           pfd_override_throttle(c->u.pfd.pf, enable);
            break;
        }
     }
@@ -3182,6 +3741,7 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
     Ssh ssh = c->ssh;
     void *sentreply = reply;
 
+    c->u.a.outstanding_requests--;
     if (!sentreply) {
        /* Fake SSH_AGENT_FAILURE. */
        sentreply = "\0\0\0\1\5";
@@ -3194,13 +3754,17 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
        send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
                    PKT_INT, c->remoteid,
                    PKT_INT, replylen,
-                   PKTT_DATA,
                    PKT_DATA, sentreply, replylen,
-                   PKTT_OTHER,
                    PKT_END);
     }
     if (reply)
        sfree(reply);
+    /*
+     * If we've already seen an incoming EOF but haven't sent an
+     * outgoing one, this may be the moment to send it.
+     */
+    if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+        sshfwd_write_eof(c);
 }
 
 /*
@@ -3236,6 +3800,59 @@ static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
     sfree(error);
 }
 
+int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint,
+                               const struct ssh_signkey *ssh2keytype,
+                               void *ssh2keydata)
+{
+    if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) {
+        return -1;                     /* no manual keys configured */
+    }
+
+    if (fingerprint) {
+        /*
+         * The fingerprint string we've been given will have things
+         * like 'ssh-rsa 2048' at the front of it. Strip those off and
+         * narrow down to just the colon-separated hex block at the
+         * end of the string.
+         */
+        const char *p = strrchr(fingerprint, ' ');
+        fingerprint = p ? p+1 : fingerprint;
+        /* Quick sanity checks, including making sure it's in lowercase */
+        assert(strlen(fingerprint) == 16*3 - 1);
+        assert(fingerprint[2] == ':');
+        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
+
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 fingerprint))
+            return 1;                  /* success */
+    }
+
+    if (ssh2keydata) {
+        /*
+         * Construct the base64-encoded public key blob and see if
+         * that's listed.
+         */
+        unsigned char *binblob;
+        char *base64blob;
+        int binlen, atoms, i;
+        binblob = ssh2keytype->public_blob(ssh2keydata, &binlen);
+        atoms = (binlen + 2) / 3;
+        base64blob = snewn(atoms * 4 + 1, char);
+        for (i = 0; i < atoms; i++)
+            base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i);
+        base64blob[atoms * 4] = '\0';
+        sfree(binblob);
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 base64blob)) {
+            sfree(base64blob);
+            return 1;                  /* success */
+        }
+        sfree(base64blob);
+    }
+
+    return 0;
+}
+
 /*
  * Handle the key exchange and user authentication phases.
  */
@@ -3244,9 +3861,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 {
     int i, j, ret;
     unsigned char cookie[8], *ptr;
-    struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     struct do_ssh1_login_state {
+       int crLine;
        int len;
        unsigned char *rsabuf, *keystr1, *keystr2;
        unsigned long supported_ciphers_mask, supported_auths_mask;
@@ -3271,10 +3888,11 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        int commentlen;
         int dlgret;
        Filename *keyfile;
+        struct RSAKey servkey, hostkey;
     };
     crState(do_ssh1_login_state);
 
-    crBegin(ssh->do_ssh1_login_crstate);
+    crBeginState;
 
     if (!pktin)
        crWaitUntil(pktin);
@@ -3293,8 +3911,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     }
     memcpy(cookie, ptr, 8);
 
-    if (!ssh1_pkt_getrsakey(pktin, &servkey, &s->keystr1) ||
-       !ssh1_pkt_getrsakey(pktin, &hostkey, &s->keystr2)) {    
+    if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) ||
+       !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) { 
        bombout(("Failed to read SSH-1 public keys from public key packet"));
        crStop(0);
     }
@@ -3306,9 +3924,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        char logmsg[80];
        logevent("Host key fingerprint is:");
        strcpy(logmsg, "      ");
-       hostkey.comment = NULL;
+       s->hostkey.comment = NULL;
        rsa_fingerprint(logmsg + strlen(logmsg),
-                       sizeof(logmsg) - strlen(logmsg), &hostkey);
+                       sizeof(logmsg) - strlen(logmsg), &s->hostkey);
        logevent(logmsg);
     }
 
@@ -3323,8 +3941,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
 
     MD5Init(&md5c);
-    MD5Update(&md5c, s->keystr2, hostkey.bytes);
-    MD5Update(&md5c, s->keystr1, servkey.bytes);
+    MD5Update(&md5c, s->keystr2, s->hostkey.bytes);
+    MD5Update(&md5c, s->keystr1, s->servkey.bytes);
     MD5Update(&md5c, cookie, 8);
     MD5Final(s->session_id, &md5c);
 
@@ -3334,13 +3952,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Verify that the `bits' and `bytes' parameters match.
      */
-    if (hostkey.bits > hostkey.bytes * 8 ||
-       servkey.bits > servkey.bytes * 8) {
+    if (s->hostkey.bits > s->hostkey.bytes * 8 ||
+       s->servkey.bits > s->servkey.bytes * 8) {
        bombout(("SSH-1 public keys were badly formatted"));
        crStop(0);
     }
 
-    s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
+    s->len = (s->hostkey.bytes > s->servkey.bytes ?
+              s->hostkey.bytes : s->servkey.bytes);
 
     s->rsabuf = snewn(s->len, unsigned char);
 
@@ -3351,35 +3970,45 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        /*
         * First format the key into a string.
         */
-       int len = rsastr_len(&hostkey);
+       int len = rsastr_len(&s->hostkey);
        char fingerprint[100];
        char *keystr = snewn(len, char);
-       rsastr_fmt(keystr, &hostkey);
-       rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
-
-        ssh_set_frozen(ssh, 1);
-       s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                        ssh->savedhost, ssh->savedport,
-                                        "rsa", keystr, fingerprint,
-                                        ssh_dialog_callback, ssh);
-       sfree(keystr);
-        if (s->dlgret < 0) {
-            do {
-                crReturn(0);
-                if (pktin) {
-                    bombout(("Unexpected data from server while waiting"
-                             " for user host key response"));
-                    crStop(0);
-                }
-            } while (pktin || inlen > 0);
-            s->dlgret = ssh->user_response;
-        }
-        ssh_set_frozen(ssh, 0);
+       rsastr_fmt(keystr, &s->hostkey);
+       rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
+
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            sfree(keystr);
+            crStop(0);
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            "rsa", keystr, fingerprint,
+                                            ssh_dialog_callback, ssh);
+            sfree(keystr);
+            if (s->dlgret < 0) {
+                do {
+                    crReturn(0);
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStop(0);
+                    }
+                } while (pktin || inlen > 0);
+                s->dlgret = ssh->user_response;
+            }
+            ssh_set_frozen(ssh, 0);
 
-        if (s->dlgret == 0) {
-           ssh_disconnect(ssh, "User aborted at host key verification",
-                          NULL, 0, TRUE);
-           crStop(0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "User aborted at host key verification",
+                               NULL, 0, TRUE);
+                crStop(0);
+            }
+        } else {
+            sfree(keystr);
         }
     }
 
@@ -3389,14 +4018,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            s->rsabuf[i] ^= s->session_id[i];
     }
 
-    if (hostkey.bytes > servkey.bytes) {
-       ret = rsaencrypt(s->rsabuf, 32, &servkey);
+    if (s->hostkey.bytes > s->servkey.bytes) {
+       ret = rsaencrypt(s->rsabuf, 32, &s->servkey);
        if (ret)
-           ret = rsaencrypt(s->rsabuf, servkey.bytes, &hostkey);
+           ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey);
     } else {
-       ret = rsaencrypt(s->rsabuf, 32, &hostkey);
+       ret = rsaencrypt(s->rsabuf, 32, &s->hostkey);
        if (ret)
-           ret = rsaencrypt(s->rsabuf, hostkey.bytes, &servkey);
+           ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey);
     }
     if (!ret) {
        bombout(("SSH-1 public key encryptions failed due to bad formatting"));
@@ -3499,21 +4128,21 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     ssh->crcda_ctx = crcda_make_context();
     logevent("Installing CRC compensation attack detector");
 
-    if (servkey.modulus) {
-       sfree(servkey.modulus);
-       servkey.modulus = NULL;
+    if (s->servkey.modulus) {
+       sfree(s->servkey.modulus);
+       s->servkey.modulus = NULL;
     }
-    if (servkey.exponent) {
-       sfree(servkey.exponent);
-       servkey.exponent = NULL;
+    if (s->servkey.exponent) {
+       sfree(s->servkey.exponent);
+       s->servkey.exponent = NULL;
     }
-    if (hostkey.modulus) {
-       sfree(hostkey.modulus);
-       hostkey.modulus = NULL;
+    if (s->hostkey.modulus) {
+       sfree(s->hostkey.modulus);
+       s->hostkey.modulus = NULL;
     }
-    if (hostkey.exponent) {
-       sfree(hostkey.exponent);
-       hostkey.exponent = NULL;
+    if (s->hostkey.exponent) {
+       sfree(s->hostkey.exponent);
+       s->hostkey.exponent = NULL;
     }
     crWaitUntil(pktin);
 
@@ -3648,7 +4277,12 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            if (s->response && s->responselen >= 5 &&
                s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
                s->p = s->response + 5;
-               s->nkeys = GET_32BIT(s->p);
+               s->nkeys = toint(GET_32BIT(s->p));
+                if (s->nkeys < 0) {
+                    logeventf(ssh, "Pageant reported negative key count %d",
+                              s->nkeys);
+                    s->nkeys = 0;
+                }
                s->p += 4;
                logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
                for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
@@ -3658,22 +4292,23 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                        int n, ok = FALSE;
                        do {           /* do while (0) to make breaking easy */
                            n = ssh1_read_bignum
-                               (s->p, s->responselen-(s->p-s->response),
+                               (s->p, toint(s->responselen-(s->p-s->response)),
                                 &s->key.exponent);
                            if (n < 0)
                                break;
                            s->p += n;
                            n = ssh1_read_bignum
-                               (s->p, s->responselen-(s->p-s->response),
+                               (s->p, toint(s->responselen-(s->p-s->response)),
                                 &s->key.modulus);
                            if (n < 0)
-                           break;
+                                break;
                            s->p += n;
                            if (s->responselen - (s->p-s->response) < 4)
                                break;
-                           s->commentlen = GET_32BIT(s->p);
+                           s->commentlen = toint(GET_32BIT(s->p));
                            s->p += 4;
-                           if (s->responselen - (s->p-s->response) <
+                           if (s->commentlen < 0 ||
+                                toint(s->responselen - (s->p-s->response)) <
                                s->commentlen)
                                break;
                            s->commentp = (char *)s->p;
@@ -3849,7 +4484,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                ret = loadrsakey(s->keyfile, &s->key, passphrase,
                                 &error);
                if (passphrase) {
-                   memset(passphrase, 0, strlen(passphrase));
+                   smemclr(passphrase, strlen(passphrase));
                    sfree(passphrase);
                }
                if (ret == 1) {
@@ -4127,9 +4762,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                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);
+                                     PKT_STR,s->cur_prompt->prompts[0]->result,
+                                    PKT_END);
                    } else {
                        for (j = 0; j < i; j++) {
                            do {
@@ -4167,9 +4801,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                    ss = s->cur_prompt->prompts[0]->result;
                }
                logevent("Sending length-padded password");
-               send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
+               send_packet(ssh, s->pwpkt_type,
                            PKT_INT, len, PKT_DATA, ss, len,
-                           PKTT_OTHER, PKT_END);
+                           PKT_END);
            } else {
                /*
                 * The server is believed unable to cope with
@@ -4179,14 +4813,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                len = strlen(s->cur_prompt->prompts[0]->result);
                logevent("Sending unpadded password");
                send_packet(ssh, s->pwpkt_type,
-                           PKTT_PASSWORD, PKT_INT, len,
+                            PKT_INT, len,
                            PKT_DATA, s->cur_prompt->prompts[0]->result, len,
-                           PKTT_OTHER, PKT_END);
+                           PKT_END);
            }
        } else {
-           send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
+           send_packet(ssh, s->pwpkt_type,
                        PKT_STR, s->cur_prompt->prompts[0]->result,
-                       PKTT_OTHER, PKT_END);
+                       PKT_END);
        }
        logevent("Sent password");
        free_prompts(s->cur_prompt);
@@ -4221,6 +4855,7 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
     if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
         return;              /* can't send EOF: pending outgoing data */
 
+    c->pending_eof = FALSE;            /* we're about to send it */
     if (ssh->version == 1) {
         send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                     PKT_END);
@@ -4231,17 +4866,14 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
         ssh2_pkt_adduint32(pktout, c->remoteid);
         ssh2_pkt_send(ssh, pktout);
         c->closes |= CLOSES_SENT_EOF;
-        if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes)) {
-            /*
-             * Also send MSG_CLOSE.
-             */
-            pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-            ssh2_pkt_adduint32(pktout, c->remoteid);
-            ssh2_pkt_send(ssh, pktout);
-            c->closes |= CLOSES_SENT_CLOSE;
-        }
+       ssh2_channel_check_close(c);
     }
-    c->pending_eof = FALSE;            /* we've sent it now */
+}
+
+Conf *sshfwd_get_conf(struct ssh_channel *c)
+{
+    Ssh ssh = c->ssh;
+    return ssh->conf;
 }
 
 void sshfwd_write_eof(struct ssh_channel *c)
@@ -4258,31 +4890,28 @@ void sshfwd_write_eof(struct ssh_channel *c)
     ssh_channel_try_eof(c);
 }
 
-void sshfwd_unclean_close(struct ssh_channel *c)
+void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
 {
     Ssh ssh = c->ssh;
-    struct Packet *pktout;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (c->closes & CLOSES_SENT_CLOSE)
-        return;
-
-    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-    ssh2_pkt_adduint32(pktout, c->remoteid);
-    ssh2_pkt_send(ssh, pktout);
-    c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
     switch (c->type) {
       case CHAN_X11:
-        x11_close(c->u.x11.s);
+        x11_close(c->u.x11.xconn);
+        logeventf(ssh, "Forwarded X11 connection terminated due to local "
+                  "error: %s", err);
         break;
       case CHAN_SOCKDATA:
       case CHAN_SOCKDATA_DORMANT:
-        pfd_close(c->u.pfd.s);
+        pfd_close(c->u.pfd.pf);
+        logeventf(ssh, "Forwarded port closed due to local error: %s", err);
         break;
     }
     c->type = CHAN_ZOMBIE;
+    c->pending_eof = FALSE;   /* this will confuse a zombie channel */
+
     ssh2_channel_check_close(c);
 }
 
@@ -4296,8 +4925,8 @@ int sshfwd_write(struct ssh_channel *c, char *buf, int len)
     if (ssh->version == 1) {
        send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
                    PKT_INT, c->remoteid,
-                   PKT_INT, len, PKTT_DATA, PKT_DATA, buf, len,
-                   PKTT_OTHER, PKT_END);
+                   PKT_INT, len, PKT_DATA, buf, len,
+                   PKT_END);
        /*
         * In SSH-1 we can return 0 here - implying that forwarded
         * connections are never individually throttled - because
@@ -4342,27 +4971,26 @@ static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
 
     if (qh->msg1 > 0) {
        assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler);
-       ssh->packet_dispatch[qh->msg1] = NULL;
+       ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1;
     }
     if (qh->msg2 > 0) {
        assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
-       ssh->packet_dispatch[qh->msg2] = NULL;
+       ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2;
     }
 
     if (qh->next) {
        ssh->qhead = qh->next;
 
        if (ssh->qhead->msg1 > 0) {
-           assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL);
+           ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
            ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
        }
        if (ssh->qhead->msg2 > 0) {
-           assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL);
+           ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
            ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler;
        }
     } else {
        ssh->qhead = ssh->qtail = NULL;
-       ssh->packet_dispatch[pktin->type] = NULL;
     }
 
     qh->handler(ssh, pktin, qh->ctx);
@@ -4386,11 +5014,11 @@ static void ssh_queue_handler(Ssh ssh, int msg1, int msg2,
        ssh->qhead = qh;
 
        if (qh->msg1 > 0) {
-           assert(ssh->packet_dispatch[qh->msg1] == NULL);
+           ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
            ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
        }
        if (qh->msg2 > 0) {
-           assert(ssh->packet_dispatch[qh->msg2] == NULL);
+           ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
            ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
        }
     } else {
@@ -4418,6 +5046,41 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
     }
 }
 
+int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
+                               void *share_ctx)
+{
+    struct ssh_rportfwd *pf = snew(struct ssh_rportfwd);
+    pf->dhost = NULL;
+    pf->dport = 0;
+    pf->share_ctx = share_ctx;
+    pf->shost = dupstr(shost);
+    pf->sport = sport;
+    pf->sportdesc = NULL;
+    if (!ssh->rportfwds) {
+        assert(ssh->version == 2);
+        ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+    }
+    if (add234(ssh->rportfwds, pf) != pf) {
+        sfree(pf->shost);
+        sfree(pf);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin,
+                                                void *ctx)
+{
+    share_got_pkt_from_server(ctx, pktin->type,
+                              pktin->body, pktin->length);
+}
+
+void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx)
+{
+    ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE,
+                      ssh_sharing_global_request_response, share_ctx);
+}
+
 static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
 {
     struct ssh_portfwd *epf;
@@ -4457,13 +5120,15 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
        if (*kp == 'L' || *kp == 'R')
            type = *kp++;
 
-       if ((kp2 = strchr(kp, ':')) != NULL) {
+       if ((kp2 = host_strchr(kp, ':')) != NULL) {
            /*
             * There's a colon in the middle of the source port
             * string, which means that the part before it is
             * actually a source address.
             */
-           saddr = dupprintf("%.*s", (int)(kp2 - kp), kp);
+           char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp);
+            saddr = host_strduptrim(saddr_tmp);
+            sfree(saddr_tmp);
            sports = kp2+1;
        } else {
            saddr = NULL;
@@ -4490,9 +5155,9 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
         } else {
             /* ordinary forwarding */
            vp = val;
-           vp2 = vp + strcspn(vp, ":");
+           vp2 = vp + host_strcspn(vp, ":");
            host = dupprintf("%.*s", (int)(vp2 - vp), vp);
-           if (vp2)
+           if (*vp2)
                vp2++;
            dports = vp2;
            dport = atoi(dports);
@@ -4604,9 +5269,9 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                        /* XXX: rport_acceptall may not represent
                         * what was used to open the original connection,
                         * since it's reconfigurable. */
-                       ssh2_pkt_addstring(pktout, "0.0.0.0");
+                       ssh2_pkt_addstring(pktout, "");
                    } else {
-                       ssh2_pkt_addstring(pktout, "127.0.0.1");
+                       ssh2_pkt_addstring(pktout, "localhost");
                    }
                    ssh2_pkt_adduint32(pktout, epf->sport);
                    ssh2_pkt_send(ssh, pktout);
@@ -4615,7 +5280,7 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                del234(ssh->rportfwds, rpf);
                free_rportfwd(rpf);
            } else if (epf->local) {
-               pfd_terminate(epf->local);
+               pfl_terminate(epf->local);
            }
 
            delpos234(ssh->portfwds, i);
@@ -4648,29 +5313,31 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
            }
 
            if (epf->type == 'L') {
-               const char *err = pfd_addforward(epf->daddr, epf->dport,
-                                                epf->saddr, epf->sport,
-                                                ssh, conf,
-                                                &epf->local,
-                                                epf->addressfamily);
+                char *err = pfl_listen(epf->daddr, epf->dport,
+                                       epf->saddr, epf->sport,
+                                       ssh, conf, &epf->local,
+                                       epf->addressfamily);
 
                logeventf(ssh, "Local %sport %s forwarding to %s%s%s",
                          epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
                          epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
                          sportdesc, dportdesc,
                          err ? " failed: " : "", err ? err : "");
+                if (err)
+                    sfree(err);
            } else if (epf->type == 'D') {
-               const char *err = pfd_addforward(NULL, -1,
-                                                epf->saddr, epf->sport,
-                                                ssh, conf,
-                                                &epf->local,
-                                                epf->addressfamily);
+               char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport,
+                                       ssh, conf, &epf->local,
+                                       epf->addressfamily);
 
                logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s",
                          epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
                          epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
                          sportdesc,
                          err ? " failed: " : "", err ? err : "");
+
+                if (err)
+                    sfree(err);
            } else {
                struct ssh_rportfwd *pf;
 
@@ -4685,9 +5352,16 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                }
 
                pf = snew(struct ssh_rportfwd);
-               strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1);
-               pf->dhost[lenof(pf->dhost)-1] = '\0';
+                pf->share_ctx = NULL;
+                pf->dhost = dupstr(epf->daddr);
                pf->dport = epf->dport;
+                if (epf->saddr) {
+                    pf->shost = dupstr(epf->saddr);
+                } else if (conf_get_int(conf, CONF_rport_acceptall)) {
+                    pf->shost = dupstr("");
+                } else {
+                    pf->shost = dupstr("localhost");
+                }
                pf->sport = epf->sport;
                if (add234(ssh->rportfwds, pf) != pf) {
                    logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
@@ -4716,14 +5390,8 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
                        pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
                        ssh2_pkt_addstring(pktout, "tcpip-forward");
                        ssh2_pkt_addbool(pktout, 1);/* want reply */
-                       if (epf->saddr) {
-                           ssh2_pkt_addstring(pktout, epf->saddr);
-                       } else if (conf_get_int(conf, CONF_rport_acceptall)) {
-                           ssh2_pkt_addstring(pktout, "0.0.0.0");
-                       } else {
-                           ssh2_pkt_addstring(pktout, "127.0.0.1");
-                       }
-                       ssh2_pkt_adduint32(pktout, epf->sport);
+                       ssh2_pkt_addstring(pktout, pf->shost);
+                       ssh2_pkt_adduint32(pktout, pf->sport);
                        ssh2_pkt_send(ssh, pktout);
 
                        ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
@@ -4773,28 +5441,19 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
        c = snew(struct ssh_channel);
        c->ssh = ssh;
 
-       if (x11_init(&c->u.x11.s, ssh->x11disp, c,
-                    NULL, -1, ssh->conf) != NULL) {
-           logevent("Opening X11 forward connection failed");
-           sfree(c);
-           send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-                       PKT_INT, remoteid, PKT_END);
-       } else {
-           logevent
-               ("Opening X11 forward connection succeeded");
-           c->remoteid = remoteid;
-           c->halfopen = FALSE;
-           c->localid = alloc_channel_id(ssh);
-           c->closes = 0;
-           c->pending_eof = FALSE;
-           c->throttling_conn = 0;
-           c->type = CHAN_X11; /* identify channel type */
-           add234(ssh->channels, c);
-           send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
-                       PKT_INT, c->remoteid, PKT_INT,
-                       c->localid, PKT_END);
-           logevent("Opened X11 forward channel");
-       }
+       c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1);
+        c->remoteid = remoteid;
+        c->halfopen = FALSE;
+        c->localid = alloc_channel_id(ssh);
+        c->closes = 0;
+        c->pending_eof = FALSE;
+        c->throttling_conn = 0;
+        c->type = CHAN_X11;    /* identify channel type */
+        add234(ssh->channels, c);
+        send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                    PKT_INT, c->remoteid, PKT_INT,
+                    c->localid, PKT_END);
+        logevent("Opened X11 forward channel");
     }
 }
 
@@ -4821,6 +5480,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
        c->type = CHAN_AGENT;   /* identify channel type */
        c->u.a.lensofar = 0;
        c->u.a.message = NULL;
+       c->u.a.outstanding_requests = 0;
        add234(ssh->channels, c);
        send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
                    PKT_INT, c->remoteid, PKT_INT, c->localid,
@@ -4832,23 +5492,17 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
 {
     /* Remote side is trying to open a channel to talk to a
      * forwarded port. Give them back a local channel number. */
-    struct ssh_channel *c;
     struct ssh_rportfwd pf, *pfp;
     int remoteid;
     int hostsize, port;
     char *host;
-    const char *e;
-    c = snew(struct ssh_channel);
-    c->ssh = ssh;
+    char *err;
 
     remoteid = ssh_pkt_getuint32(pktin);
     ssh_pkt_getstring(pktin, &host, &hostsize);
     port = ssh_pkt_getuint32(pktin);
 
-    if (hostsize >= lenof(pf.dhost))
-       hostsize = lenof(pf.dhost)-1;
-    memcpy(pf.dhost, host, hostsize);
-    pf.dhost[hostsize] = '\0';
+    pf.dhost = dupprintf("%.*s", hostsize, host);
     pf.dport = port;
     pfp = find234(ssh->rportfwds, &pf, NULL);
 
@@ -4858,12 +5512,16 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
        send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
                    PKT_INT, remoteid, PKT_END);
     } else {
+        struct ssh_channel *c = snew(struct ssh_channel);
+        c->ssh = ssh;
+
        logeventf(ssh, "Received remote port open request for %s:%d",
                  pf.dhost, port);
-       e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port,
-                          c, ssh->conf, pfp->pfrec->addressfamily);
-       if (e != NULL) {
-           logeventf(ssh, "Port open failed: %s", e);
+       err = pfd_connect(&c->u.pfd.pf, pf.dhost, port,
+                          c, ssh->conf, pfp->pfrec->addressfamily);
+       if (err != NULL) {
+           logeventf(ssh, "Port open failed: %s", err);
+            sfree(err);
            sfree(c);
            send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
                        PKT_INT, remoteid, PKT_END);
@@ -4882,6 +5540,8 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
            logevent("Forwarded port opened successfully");
        }
     }
+
+    sfree(pf.dhost);
 }
 
 static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
@@ -4896,7 +5556,7 @@ static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
        c->halfopen = FALSE;
        c->type = CHAN_SOCKDATA;
        c->throttling_conn = 0;
-       pfd_confirm(c->u.pfd.s);
+       pfd_confirm(c->u.pfd.pf);
     }
 
     if (c && c->pending_eof) {
@@ -4918,7 +5578,7 @@ static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
     c = find234(ssh->channels, &remoteid, ssh_channelfind);
     if (c && c->type == CHAN_SOCKDATA_DORMANT) {
        logevent("Forwarded connection refused by server");
-       pfd_close(c->u.pfd.s);
+       pfd_close(c->u.pfd.pf);
        del234(ssh->channels, c);
        sfree(c);
     }
@@ -4944,17 +5604,20 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
 
             switch (c->type) {
               case CHAN_X11:
-                if (c->u.x11.s)
-                    x11_send_eof(c->u.x11.s);
+                if (c->u.x11.xconn)
+                    x11_send_eof(c->u.x11.xconn);
                 else
                     send_close = TRUE;
+               break;
               case CHAN_SOCKDATA:
-                if (c->u.pfd.s)
-                    x11_send_eof(c->u.pfd.s);
+                if (c->u.pfd.pf)
+                    pfd_send_eof(c->u.pfd.pf);
                 else
                     send_close = TRUE;
+               break;
               case CHAN_AGENT:
                 send_close = TRUE;
+               break;
             }
 
             if (send_close && !(c->closes & CLOSES_SENT_EOF)) {
@@ -5007,10 +5670,10 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
        int bufsize = 0;
        switch (c->type) {
          case CHAN_X11:
-           bufsize = x11_send(c->u.x11.s, p, len);
+           bufsize = x11_send(c->u.x11.xconn, p, len);
            break;
          case CHAN_SOCKDATA:
-           bufsize = pfd_send(c->u.pfd.s, p, len);
+           bufsize = pfd_send(c->u.pfd.pf, p, len);
            break;
          case CHAN_AGENT:
            /* Data for an agent message. Buffer it. */
@@ -5043,6 +5706,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar == c->u.a.totallen) {
                    void *reply;
                    int replylen;
+                    c->u.a.outstanding_requests++;
                    if (agent_query(c->u.a.message,
                                    c->u.a.totallen,
                                    &reply, &replylen,
@@ -5097,6 +5761,10 @@ static void ssh1_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_addbyte(pktout, arg);
 }
 
+int ssh_agent_forwarding_permitted(Ssh ssh)
+{
+    return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists();
+}
 
 static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
                               struct Packet *pktin)
@@ -5117,7 +5785,7 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data;
     ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status;
 
-    if (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
+    if (ssh_agent_forwarding_permitted(ssh)) {
        logevent("Requesting agent forwarding");
        send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
        do {
@@ -5136,47 +5804,47 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        }
     }
 
-    if (conf_get_int(ssh->conf, CONF_x11_forward) &&
-       (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
-                                         conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
-       logevent("Requesting X11 forwarding");
-       /*
-        * 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, ssh->x11disp->remoteauthprotoname,
-                       PKTT_PASSWORD,
-                       PKT_STR, ssh->x11disp->remoteauthdatastring,
-                       PKTT_OTHER,
-                       PKT_INT, ssh->x11disp->screennum,
-                       PKT_END);
-       } else {
-           send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-                       PKT_STR, ssh->x11disp->remoteauthprotoname,
-                       PKTT_PASSWORD,
-                       PKT_STR, ssh->x11disp->remoteauthdatastring,
-                       PKTT_OTHER,
-                       PKT_END);
-       }
-       do {
-           crReturnV;
-       } while (!pktin);
-       if (pktin->type != SSH1_SMSG_SUCCESS
-           && pktin->type != SSH1_SMSG_FAILURE) {
-           bombout(("Protocol confusion"));
-           crStopV;
-       } else if (pktin->type == SSH1_SMSG_FAILURE) {
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
-           ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open;
-       }
+    if (conf_get_int(ssh->conf, CONF_x11_forward)) {
+        ssh->x11disp =
+            x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+                              ssh->conf);
+        if (!ssh->x11disp) {
+            /* FIXME: return an error message from x11_setup_display */
+            logevent("X11 forwarding not enabled: unable to"
+                     " initialise X display");
+        } else {
+            ssh->x11auth = x11_invent_fake_auth
+                (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth));
+            ssh->x11auth->disp = ssh->x11disp;
+
+            logevent("Requesting X11 forwarding");
+            if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
+                send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+                            PKT_STR, ssh->x11auth->protoname,
+                            PKT_STR, ssh->x11auth->datastring,
+                            PKT_INT, ssh->x11disp->screennum,
+                            PKT_END);
+            } else {
+                send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+                            PKT_STR, ssh->x11auth->protoname,
+                            PKT_STR, ssh->x11auth->datastring,
+                            PKT_END);
+            }
+            do {
+                crReturnV;
+            } while (!pktin);
+            if (pktin->type != SSH1_SMSG_SUCCESS
+                && pktin->type != SSH1_SMSG_FAILURE) {
+                bombout(("Protocol confusion"));
+                crStopV;
+            } else if (pktin->type == SSH1_SMSG_FAILURE) {
+                logevent("X11 forwarding refused");
+            } else {
+                logevent("X11 forwarding enabled");
+                ssh->X11_fwd_enabled = TRUE;
+                ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open;
+            }
+        }
     }
 
     ssh_setup_portfwd(ssh, ssh->conf);
@@ -5271,7 +5939,7 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        ssh_special(ssh, TS_EOF);
 
     if (ssh->ldisc)
-       ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+       ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
     ssh->send_ok = 1;
     ssh->channels = newtree234(ssh_channelcmp);
     while (1) {
@@ -5297,8 +5965,8 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
            while (inlen > 0) {
                int len = min(inlen, 512);
                send_packet(ssh, SSH1_CMSG_STDIN_DATA,
-                           PKT_INT, len,  PKTT_DATA, PKT_DATA, in, len,
-                           PKTT_OTHER, PKT_END);
+                           PKT_INT, len, PKT_DATA, in, len,
+                            PKT_END);
                in += len;
                inlen -= len;
            }
@@ -5378,7 +6046,8 @@ static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
 /*
  * Utility routine for decoding comma-separated strings in KEXINIT.
  */
-static int in_commasep_string(char *needle, char *haystack, int haylen)
+static int in_commasep_string(char const *needle, char const *haystack,
+                             int haylen)
 {
     int needlen;
     if (!needle || !haystack)         /* protect against null pointers */
@@ -5409,7 +6078,8 @@ static int in_commasep_string(char *needle, char *haystack, int haylen)
 /*
  * Similar routine for checking whether we have the first string in a list.
  */
-static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+static int first_in_commasep_string(char const *needle, char const *haystack,
+                                   int haylen)
 {
     int needlen;
     if (!needle || !haystack)         /* protect against null pointers */
@@ -5427,6 +6097,19 @@ static int first_in_commasep_string(char *needle, char *haystack, int haylen)
     return 0;
 }
 
+/*
+ * Add a value to the comma-separated string at the end of the packet.
+ * If the value is already in the string, don't bother adding it again.
+ */
+static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data)
+{
+    if (in_commasep_string(data, (char *)pkt->data + pkt->savedpos,
+                          pkt->length - pkt->savedpos)) return;
+    if (pkt->length - pkt->savedpos > 0)
+       ssh_pkt_addstring_str(pkt, ",");
+    ssh_pkt_addstring_str(pkt, data);
+}
+
 
 /*
  * SSH-2 key creation method.
@@ -5459,11 +6142,12 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
 /*
  * Handle the SSH-2 transport layer.
  */
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                             struct Packet *pktin)
 {
     unsigned char *in = (unsigned char *)vin;
     struct do_ssh2_transport_state {
+       int crLine;
        int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
        Bignum p, g, e, f, K;
        void *our_kexinit;
@@ -5475,12 +6159,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        const struct ssh2_cipher *sccipher_tobe;
        const struct ssh_mac *csmac_tobe;
        const struct ssh_mac *scmac_tobe;
+        int csmac_etm_tobe, scmac_etm_tobe;
        const struct ssh_compress *cscomp_tobe;
        const struct ssh_compress *sccomp_tobe;
        char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
        int hostkeylen, siglen, rsakeylen;
        void *hkey;                    /* actual host key */
        void *rsakey;                  /* for RSA kex */
+        void *eckey;                   /* for ECDH kex */
        unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
        const struct ssh_kexes *preferred_kex[KEX_MAX];
@@ -5497,7 +6183,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     };
     crState(do_ssh2_transport_state);
 
-    crBegin(ssh->do_ssh2_transport_crstate);
+    assert(!ssh->bare_connection);
+
+    crBeginState;
 
     s->cscipher_tobe = s->sccipher_tobe = NULL;
     s->csmac_tobe = s->scmac_tobe = NULL;
@@ -5518,7 +6206,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
   begin_key_exchange:
     ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
     {
-       int i, j, commalist_started;
+       int i, j, k;
 
        /*
         * Set up the preferred key exchange. (NULL => warn below here)
@@ -5542,6 +6230,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                s->preferred_kex[s->n_preferred_kex++] =
                    &ssh_rsa_kex;
                break;
+              case KEX_ECDH:
+                s->preferred_kex[s->n_preferred_kex++] =
+                    &ssh_ecdh_kex;
+                break;
              case KEX_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
@@ -5612,63 +6304,54 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
        /* List key exchange algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
-       commalist_started = 0;
        for (i = 0; i < s->n_preferred_kex; i++) {
            const struct ssh_kexes *k = s->preferred_kex[i];
            if (!k) continue;          /* warning flag */
-           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;
-           }
+           for (j = 0; j < k->nkexes; j++)
+               ssh2_pkt_addstring_commasep(s->pktout, k->list[j]->name);
        }
        /* List server host key algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       for (i = 0; i < lenof(hostkey_algs); i++) {
-           ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
-           if (i < lenof(hostkey_algs) - 1)
-               ssh2_pkt_addstring_str(s->pktout, ",");
-       }
-       /* List client->server encryption algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       commalist_started = 0;
-       for (i = 0; i < s->n_preferred_ciphers; i++) {
-           const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-           if (!c) continue;          /* warning flag */
-           for (j = 0; j < c->nciphers; j++) {
-               if (commalist_started)
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
-               commalist_started = 1;
-           }
-       }
-       /* List server->client encryption algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       commalist_started = 0;
-       for (i = 0; i < s->n_preferred_ciphers; i++) {
-           const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-           if (!c) continue; /* warning flag */
-           for (j = 0; j < c->nciphers; j++) {
-               if (commalist_started)
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
-               commalist_started = 1;
+        if (!s->got_session_id) {
+            /*
+             * In the first key exchange, we list all the algorithms
+             * we're prepared to cope with.
+             */
+            ssh2_pkt_addstring_start(s->pktout);
+            for (i = 0; i < lenof(hostkey_algs); i++)
+                ssh2_pkt_addstring_commasep(s->pktout, hostkey_algs[i]->name);
+        } else {
+            /*
+             * In subsequent key exchanges, we list only the kex
+             * algorithm that was selected in the first key exchange,
+             * so that we keep getting the same host key and hence
+             * don't have to interrupt the user's session to ask for
+             * reverification.
+             */
+            assert(ssh->kex);
+            ssh2_pkt_addstring(s->pktout, ssh->hostkey->name);
+        }
+       /* List encryption algorithms (client->server then server->client). */
+       for (k = 0; k < 2; k++) {
+           ssh2_pkt_addstring_start(s->pktout);
+           for (i = 0; i < s->n_preferred_ciphers; i++) {
+               const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+               if (!c) continue;              /* warning flag */
+               for (j = 0; j < c->nciphers; j++)
+                   ssh2_pkt_addstring_commasep(s->pktout, c->list[j]->name);
            }
        }
-       /* List client->server MAC algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       for (i = 0; i < s->nmacs; i++) {
-           ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
-           if (i < s->nmacs - 1)
-               ssh2_pkt_addstring_str(s->pktout, ",");
-       }
-       /* List server->client MAC algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       for (i = 0; i < s->nmacs; i++) {
-           ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
-           if (i < s->nmacs - 1)
-               ssh2_pkt_addstring_str(s->pktout, ",");
+       /* List MAC algorithms (client->server then server->client). */
+       for (j = 0; j < 2; j++) {
+           ssh2_pkt_addstring_start(s->pktout);
+           for (i = 0; i < s->nmacs; i++) {
+               ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->name);
+            }
+           for (i = 0; i < s->nmacs; i++) {
+                /* For each MAC, there may also be an ETM version,
+                 * which we list second. */
+                if (s->maclist[i]->etm_name)
+                    ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->etm_name);
+            }
        }
        /* List client->server compression algorithms,
         * then server->client compression algorithms. (We use the
@@ -5677,25 +6360,18 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            ssh2_pkt_addstring_start(s->pktout);
            assert(lenof(compressions) > 1);
            /* Prefer non-delayed versions */
-           ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+           ssh2_pkt_addstring_commasep(s->pktout, s->preferred_comp->name);
            /* We don't even list delayed versions of algorithms until
             * they're allowed to be used, to avoid a race. See the end of
             * this function. */
-           if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
-               ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout,
-                                      s->preferred_comp->delayed_name);
-           }
+           if (s->userauth_succeeded && s->preferred_comp->delayed_name)
+               ssh2_pkt_addstring_commasep(s->pktout,
+                                           s->preferred_comp->delayed_name);
            for (i = 0; i < lenof(compressions); i++) {
                const struct ssh_compress *c = compressions[i];
-               if (c != s->preferred_comp) {
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-                   ssh2_pkt_addstring_str(s->pktout, c->name);
-                   if (s->userauth_succeeded && c->delayed_name) {
-                       ssh2_pkt_addstring_str(s->pktout, ",");
-                       ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
-                   }
-               }
+               ssh2_pkt_addstring_commasep(s->pktout, c->name);
+               if (s->userauth_succeeded && c->delayed_name)
+                   ssh2_pkt_addstring_commasep(s->pktout, c->delayed_name);
            }
        }
        /* List client->server languages. Empty list. */
@@ -5715,7 +6391,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     ssh2_pkt_send_noqueue(ssh, s->pktout);
 
     if (!pktin)
-       crWaitUntil(pktin);
+       crWaitUntilV(pktin);
 
     /*
      * Now examine the other side's KEXINIT to see what we're up
@@ -5727,7 +6403,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
        if (pktin->type != SSH2_MSG_KEXINIT) {
            bombout(("expected key exchange packet from server"));
-           crStop(0);
+           crStopV;
        }
        ssh->kex = NULL;
        ssh->hostkey = NULL;
@@ -5741,6 +6417,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
        pktin->savedpos += 16;          /* skip garbage cookie */
        ssh_pkt_getstring(pktin, &str, &len);    /* key exchange algorithms */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
 
        preferred = NULL;
        for (i = 0; i < s->n_preferred_kex; i++) {
@@ -5760,9 +6440,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!ssh->kex) {
-           bombout(("Couldn't agree a key exchange algorithm (available: %s)",
-                    str ? str : "(null)"));
-           crStop(0);
+            bombout(("Couldn't agree a key exchange algorithm"
+                     " (available: %.*s)", len, str));
+           crStopV;
        }
        /*
         * Note that the server's guess is considered wrong if it doesn't match
@@ -5771,15 +6451,29 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         */
        s->guessok = first_in_commasep_string(preferred, str, len);
        ssh_pkt_getstring(pktin, &str, &len);    /* host key algorithms */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(hostkey_algs); i++) {
            if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
                ssh->hostkey = hostkey_algs[i];
                break;
            }
        }
+       if (!ssh->hostkey) {
+            bombout(("Couldn't agree a host key algorithm"
+                     " (available: %.*s)", len, str));
+           crStopV;
+       }
+
        s->guessok = s->guessok &&
            first_in_commasep_string(hostkey_algs[0]->name, str, len);
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server cipher */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) {
@@ -5796,12 +6490,16 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!s->cscipher_tobe) {
-           bombout(("Couldn't agree a client-to-server cipher (available: %s)",
-                    str ? str : "(null)"));
-           crStop(0);
+            bombout(("Couldn't agree a client-to-server cipher"
+                     " (available: %.*s)", len, str));
+           crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client cipher */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) {
@@ -5818,26 +6516,70 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!s->sccipher_tobe) {
-           bombout(("Couldn't agree a server-to-client cipher (available: %s)",
-                    str ? str : "(null)"));
-           crStop(0);
+            bombout(("Couldn't agree a server-to-client cipher"
+                     " (available: %.*s)", len, str));
+           crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server mac */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->csmac_tobe = s->maclist[i];
-               break;
+               s->csmac_etm_tobe = FALSE;
+                break;
            }
        }
+        if (!s->csmac_tobe) {
+            for (i = 0; i < s->nmacs; i++) {
+                if (s->maclist[i]->etm_name &&
+                    in_commasep_string(s->maclist[i]->etm_name, str, len)) {
+                    s->csmac_tobe = s->maclist[i];
+                    s->csmac_etm_tobe = TRUE;
+                    break;
+                }
+            }
+        }
+        if (!s->csmac_tobe) {
+            bombout(("Couldn't agree a client-to-server MAC"
+                     " (available: %.*s)", len, str));
+           crStopV;
+        }
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client mac */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->scmac_tobe = s->maclist[i];
-               break;
+               s->scmac_etm_tobe = FALSE;
+                break;
            }
        }
+        if (!s->scmac_tobe) {
+            for (i = 0; i < s->nmacs; i++) {
+                if (s->maclist[i]->etm_name &&
+                    in_commasep_string(s->maclist[i]->etm_name, str, len)) {
+                    s->scmac_tobe = s->maclist[i];
+                    s->scmac_etm_tobe = TRUE;
+                    break;
+                }
+            }
+        }
+        if (!s->scmac_tobe) {
+            bombout(("Couldn't agree a server-to-client MAC"
+                     " (available: %.*s)", len, str));
+           crStopV;
+        }
        ssh_pkt_getstring(pktin, &str, &len);  /* client->server compression */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(compressions) + 1; i++) {
            const struct ssh_compress *c =
                i == 0 ? s->preferred_comp : compressions[i - 1];
@@ -5854,6 +6596,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client compression */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(compressions) + 1; i++) {
            const struct ssh_compress *c =
                i == 0 ? s->preferred_comp : compressions[i - 1];
@@ -5877,6 +6623,16 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client language */
        s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
 
+       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);
+        /* Include the type byte in the hash of server's KEXINIT */
+        hash_string(ssh->kex->hash, ssh->exhash,
+                    pktin->body - 1, pktin->length + 1);
+
        if (s->warn_kex) {
            ssh_set_frozen(ssh, 1);
            s->dlgret = askalg(ssh->frontend, "key-exchange algorithm",
@@ -5884,11 +6640,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5897,7 +6653,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at kex warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
@@ -5909,11 +6665,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5922,7 +6678,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
@@ -5934,11 +6690,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                               ssh_dialog_callback, ssh);
            if (s->dlgret < 0) {
                do {
-                   crReturn(0);
+                   crReturnV;
                    if (pktin) {
                        bombout(("Unexpected data from server while"
                                 " waiting for user response"));
-                       crStop(0);
+                       crStopV;
                    }
                } while (pktin || inlen > 0);
                s->dlgret = ssh->user_response;
@@ -5947,22 +6703,12 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (s->dlgret == 0) {
                ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
                               0, TRUE);
-               crStop(0);
+               crStopV;
            }
        }
 
-       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 */
+           crWaitUntilV(pktin);                /* Ignore packet */
     }
 
     if (ssh->kex->main_type == KEXTYPE_DH) {
@@ -5995,20 +6741,31 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
              * much data.
              */
             s->pbits = 512 << ((s->nbits - 1) / 64);
-            s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
-            ssh2_pkt_adduint32(s->pktout, s->pbits);
+            if (s->pbits < DH_MIN_SIZE)
+                s->pbits = DH_MIN_SIZE;
+            if (s->pbits > DH_MAX_SIZE)
+                s->pbits = DH_MAX_SIZE;
+            if ((ssh->remote_bugs & BUG_SSH2_OLDGEX)) {
+                s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
+                ssh2_pkt_adduint32(s->pktout, s->pbits);
+            } else {
+                s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
+                ssh2_pkt_adduint32(s->pktout, DH_MIN_SIZE);
+                ssh2_pkt_adduint32(s->pktout, s->pbits);
+                ssh2_pkt_adduint32(s->pktout, DH_MAX_SIZE);
+            }
             ssh2_pkt_send_noqueue(ssh, s->pktout);
 
-            crWaitUntil(pktin);
+            crWaitUntilV(pktin);
             if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
                 bombout(("expected key exchange group packet from server"));
-                crStop(0);
+                crStopV;
             }
             s->p = ssh2_pkt_getmp(pktin);
             s->g = ssh2_pkt_getmp(pktin);
             if (!s->p || !s->g) {
                 bombout(("unable to read mp-ints from incoming group packet"));
-                crStop(0);
+                crStopV;
             }
             ssh->kex_ctx = dh_setup_gex(s->p, s->g);
             s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
@@ -6034,10 +6791,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         ssh2_pkt_send_noqueue(ssh, s->pktout);
 
         set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != s->kex_reply_value) {
             bombout(("expected key exchange reply packet from server"));
-            crStop(0);
+            crStopV;
         }
         set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
         ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
@@ -6045,10 +6802,17 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         s->f = ssh2_pkt_getmp(pktin);
         if (!s->f) {
             bombout(("unable to parse key exchange reply packet"));
-            crStop(0);
+            crStopV;
         }
         ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
 
+        {
+            const char *err = dh_validate_f(ssh->kex_ctx, s->f);
+            if (err) {
+                bombout(("key exchange reply failed validation: %s", err));
+                crStopV;
+            }
+        }
         s->K = dh_find_K(ssh->kex_ctx, s->f);
 
         /* We assume everything from now on will be quick, and it might
@@ -6057,7 +6821,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
         hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
         if (!ssh->kex->pdata) {
+            if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX))
+                hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE);
             hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
+            if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX))
+                hash_uint32(ssh->kex->hash, ssh->exhash, DH_MAX_SIZE);
             hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
             hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
         }
@@ -6070,6 +6838,83 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
             freebn(s->g);
             freebn(s->p);
         }
+    } else if (ssh->kex->main_type == KEXTYPE_ECDH) {
+
+        logeventf(ssh, "Doing ECDH key exchange with hash %s",
+                  ssh->kex->hash->text_name);
+        ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX;
+
+        s->eckey = NULL;
+        if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp256")) {
+            s->eckey = ssh_ecdhkex_newkey(ec_p256());
+        } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp384")) {
+            s->eckey = ssh_ecdhkex_newkey(ec_p384());
+        } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp521")) {
+            s->eckey = ssh_ecdhkex_newkey(ec_p521());
+        }
+        if (!s->eckey) {
+            bombout(("Unable to generate key for ECDH"));
+            crStopV;
+        }
+
+        {
+            char *publicPoint;
+            int publicPointLength;
+            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+            if (!publicPoint) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("Unable to encode public key for ECDH"));
+                crStopV;
+            }
+            s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT);
+            ssh2_pkt_addstring_start(s->pktout);
+            ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength);
+            sfree(publicPoint);
+        }
+
+        ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+        crWaitUntilV(pktin);
+        if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+            ssh_ecdhkex_freekey(s->eckey);
+            bombout(("expected ECDH reply packet from server"));
+            crStopV;
+        }
+
+        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 *publicPoint;
+            int publicPointLength;
+            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+            if (!publicPoint) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("Unable to encode public key for ECDH hash"));
+                crStopV;
+            }
+            hash_string(ssh->kex->hash, ssh->exhash,
+                        publicPoint, publicPointLength);
+            sfree(publicPoint);
+        }
+
+        {
+            char *keydata;
+            int keylen;
+            ssh_pkt_getstring(pktin, &keydata, &keylen);
+            hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen);
+            s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen);
+            if (!s->K) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("point received in ECDH was not valid"));
+                crStopV;
+            }
+        }
+
+        ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+        ssh_ecdhkex_freekey(s->eckey);
     } else {
        logeventf(ssh, "Doing RSA key exchange with hash %s",
                  ssh->kex->hash->text_name);
@@ -6078,10 +6923,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * RSA key exchange. First expect a KEXRSA_PUBKEY packet
          * from the server.
          */
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
             bombout(("expected RSA public key packet from server"));
-            crStop(0);
+            crStopV;
         }
 
         ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
@@ -6100,7 +6945,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         if (!s->rsakey) {
             sfree(s->rsakeydata);
             bombout(("unable to parse RSA public key from server"));
-            crStop(0);
+            crStopV;
         }
 
         hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
@@ -6160,11 +7005,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
         ssh_rsakex_freekey(s->rsakey);
 
-        crWaitUntil(pktin);
+        crWaitUntilV(pktin);
         if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
             sfree(s->rsakeydata);
             bombout(("expected signature packet from server"));
-            crStop(0);
+            crStopV;
         }
 
         ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
@@ -6188,44 +7033,68 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                                 (char *)s->exchange_hash,
                                 ssh->kex->hash->hlen)) {
        bombout(("Server's host key did not match the signature supplied"));
-       crStop(0);
+       crStopV;
     }
 
-    /*
-     * Authenticate remote host: verify host key. (We've already
-     * checked the signature of the exchange hash.)
-     */
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
-    s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
-    ssh_set_frozen(ssh, 1);
-    s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                    ssh->savedhost, ssh->savedport,
-                                    ssh->hostkey->keytype, s->keystr,
-                                   s->fingerprint,
-                                    ssh_dialog_callback, ssh);
-    if (s->dlgret < 0) {
-        do {
-            crReturn(0);
-            if (pktin) {
-                bombout(("Unexpected data from server while waiting"
-                         " for user host key response"));
-                    crStop(0);
+    if (!s->got_session_id) {
+        /*
+         * Authenticate remote host: verify host key. (We've already
+         * checked the signature of the exchange hash.)
+         */
+        s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+        logevent("Host key fingerprint is:");
+        logevent(s->fingerprint);
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint,
+                                               ssh->hostkey, s->hkey);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            crStopV;
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            ssh->hostkey->keytype, s->keystr,
+                                            s->fingerprint,
+                                            ssh_dialog_callback, ssh);
+            if (s->dlgret < 0) {
+                do {
+                    crReturnV;
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStopV;
+                    }
+                } while (pktin || inlen > 0);
+                s->dlgret = ssh->user_response;
             }
-        } while (pktin || inlen > 0);
-        s->dlgret = ssh->user_response;
-    }
-    ssh_set_frozen(ssh, 0);
-    if (s->dlgret == 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 */
-       logevent("Host key fingerprint is:");
-       logevent(s->fingerprint);
+            ssh_set_frozen(ssh, 0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "Aborted at host key verification", NULL,
+                               0, TRUE);
+                crStopV;
+            }
+        }
+        sfree(s->fingerprint);
+        /*
+         * Save this host key, to check against the one presented in
+         * subsequent rekeys.
+         */
+        ssh->hostkey_str = s->keystr;
+    } else {
+        /*
+         * In a rekey, we never present an interactive host key
+         * verification request to the user. Instead, we simply
+         * enforce that the key we're seeing this time is identical to
+         * the one we saw before.
+         */
+        if (strcmp(ssh->hostkey_str, s->keystr)) {
+            bombout(("Host key was different in repeat key exchange"));
+            crStopV;
+        }
+        sfree(s->keystr);
     }
-    sfree(s->fingerprint);
-    sfree(s->keystr);
     ssh->hostkey->freekey(s->hkey);
 
     /*
@@ -6261,6 +7130,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (ssh->cs_mac_ctx)
        ssh->csmac->free_context(ssh->cs_mac_ctx);
     ssh->csmac = s->csmac_tobe;
+    ssh->csmac_etm = s->csmac_etm_tobe;
     ssh->cs_mac_ctx = ssh->csmac->make_context();
 
     if (ssh->cs_comp_ctx)
@@ -6287,13 +7157,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        assert(ssh->csmac->len <=
               ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
-       memset(keyspace, 0, sizeof(keyspace));
+       smemclr(keyspace, sizeof(keyspace));
     }
 
     logeventf(ssh, "Initialised %.200s client->server encryption",
              ssh->cscipher->text_name);
-    logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
-             ssh->csmac->text_name);
+    logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s",
+             ssh->csmac->text_name,
+              ssh->csmac_etm ? " (in ETM mode)" : "");
     if (ssh->cscomp->text_name)
        logeventf(ssh, "Initialised %s compression",
                  ssh->cscomp->text_name);
@@ -6308,10 +7179,10 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     /*
      * Expect SSH2_MSG_NEWKEYS from server.
      */
-    crWaitUntil(pktin);
+    crWaitUntilV(pktin);
     if (pktin->type != SSH2_MSG_NEWKEYS) {
        bombout(("expected new-keys packet from server"));
-       crStop(0);
+       crStopV;
     }
     ssh->incoming_data_size = 0;       /* start counting from here */
 
@@ -6327,6 +7198,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (ssh->sc_mac_ctx)
        ssh->scmac->free_context(ssh->sc_mac_ctx);
     ssh->scmac = s->scmac_tobe;
+    ssh->scmac_etm = s->scmac_etm_tobe;
     ssh->sc_mac_ctx = ssh->scmac->make_context();
 
     if (ssh->sc_comp_ctx)
@@ -6353,12 +7225,13 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        assert(ssh->scmac->len <=
               ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
-       memset(keyspace, 0, sizeof(keyspace));
+       smemclr(keyspace, sizeof(keyspace));
     }
     logeventf(ssh, "Initialised %.200s server->client encryption",
              ssh->sccipher->text_name);
-    logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
-             ssh->scmac->text_name);
+    logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s",
+             ssh->scmac->text_name,
+              ssh->scmac_etm ? " (in ETM mode)" : "");
     if (ssh->sccomp->text_name)
        logeventf(ssh, "Initialised %s decompression",
                  ssh->sccomp->text_name);
@@ -6388,19 +7261,6 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
                                         ssh2_timer, ssh);
 
-    /*
-     * If this is the first key exchange phase, we must pass the
-     * SSH2_MSG_NEWKEYS packet to the next layer, not because it
-     * wants to see it but because it will need time to initialise
-     * itself before it sees an actual packet. In subsequent key
-     * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
-     * it would only confuse the layer above.
-     */
-    if (s->activated_authconn) {
-       crReturn(0);
-    }
-    s->activated_authconn = TRUE;
-
     /*
      * Now we're encrypting. Begin returning 1 to the protocol main
      * function so that other things can run on top of the
@@ -6419,7 +7279,14 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
             (!pktin && inlen < 0))) {
         wait_for_rekey:
-       crReturn(1);
+       if (!ssh->protocol_initial_phase_done) {
+           ssh->protocol_initial_phase_done = TRUE;
+           /*
+            * Allow authconn to initialise itself.
+            */
+           do_ssh2_authconn(ssh, NULL, 0, NULL);
+       }
+       crReturnV;
     }
     if (pktin) {
        logevent("Server initiated key re-exchange");
@@ -6478,7 +7345,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     goto begin_key_exchange;
 
-    crFinish(1);
+    crFinishV;
 }
 
 /*
@@ -6510,9 +7377,7 @@ static int ssh2_try_send(struct ssh_channel *c)
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
        ssh2_pkt_adduint32(pktout, c->remoteid);
        ssh2_pkt_addstring_start(pktout);
-       dont_log_data(ssh, pktout, PKTLOG_OMIT);
        ssh2_pkt_addstring_data(pktout, data, len);
-       end_log_omission(ssh, pktout);
        ssh2_pkt_send(ssh, pktout);
        bufchain_consume(&c->v.v2.outbuffer, len);
        c->v.v2.remwindow -= len;
@@ -6547,19 +7412,32 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
             * notification since it will be polled */
            break;
          case CHAN_X11:
-           x11_unthrottle(c->u.x11.s);
+           x11_unthrottle(c->u.x11.xconn);
            break;
          case CHAN_AGENT:
            /* agent sockets are request/response and need no
             * buffer management */
            break;
          case CHAN_SOCKDATA:
-           pfd_unthrottle(c->u.pfd.s);
+           pfd_unthrottle(c->u.pfd.pf);
            break;
        }
     }
 }
 
+static int ssh_is_simple(Ssh ssh)
+{
+    /*
+     * We use the 'simple' variant of the SSH protocol if we're asked
+     * to, except not if we're also doing connection-sharing (either
+     * tunnelling our packets over an upstream or expecting to be
+     * tunnelled over ourselves), since then the assumption that we
+     * have only one channel to worry about is not true after all.
+     */
+    return (conf_get_int(ssh->conf, CONF_ssh_simple) &&
+            !ssh->bare_connection && !ssh->connshare);
+}
+
 /*
  * Set up most of a new ssh_channel for SSH-2.
  */
@@ -6571,15 +7449,82 @@ static void ssh2_channel_init(struct ssh_channel *c)
     c->pending_eof = FALSE;
     c->throttling_conn = FALSE;
     c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
-       conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
-    c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+       ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+    c->v.v2.chanreq_head = NULL;
     c->v.v2.throttle_state = UNTHROTTLED;
     bufchain_init(&c->v.v2.outbuffer);
 }
 
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type)
+{
+    struct Packet *pktout;
+
+    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+    ssh2_pkt_addstring(pktout, type);
+    ssh2_pkt_adduint32(pktout, c->localid);
+    ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
+    ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT);      /* our max pkt size */
+    return pktout;
+}
+
+/*
+ * CHANNEL_FAILURE doesn't come with any indication of what message
+ * caused it, so we have to keep track of the outstanding
+ * CHANNEL_REQUESTs ourselves.
+ */
+static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
+                                      cchandler_fn_t handler, void *ctx)
+{
+    struct outstanding_channel_request *ocr =
+       snew(struct outstanding_channel_request);
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    ocr->handler = handler;
+    ocr->ctx = ctx;
+    ocr->next = NULL;
+    if (!c->v.v2.chanreq_head)
+       c->v.v2.chanreq_head = ocr;
+    else
+       c->v.v2.chanreq_tail->next = ocr;
+    c->v.v2.chanreq_tail = ocr;
+}
+
+/*
+ * Construct the common parts of a CHANNEL_REQUEST.  If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives.  The returned packet is ready to have any
+ * request-specific data added and be sent.  Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin. If
+ * pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed, or because
+ * the server initiated channel closure before we saw the response)
+ * and the handler should free any storage it's holding.
+ */
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+                                       cchandler_fn_t handler, void *ctx)
+{
+    struct Packet *pktout;
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(pktout, c->remoteid);
+    ssh2_pkt_addstring(pktout, type);
+    ssh2_pkt_addbool(pktout, handler != NULL);
+    if (handler != NULL)
+       ssh2_queue_chanreq_handler(c, handler, ctx);
+    return pktout;
+}
+
 /*
  * Potentially enlarge the window on an SSH-2 channel.
  */
+static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *,
+                                       void *);
 static void ssh2_set_window(struct ssh_channel *c, int newwin)
 {
     Ssh ssh = c->ssh;
@@ -6593,6 +7538,14 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
     if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
        return;
 
+    /*
+     * Also, never widen the window for an X11 channel when we're
+     * still waiting to see its initial auth and may yet hand it off
+     * to a downstream.
+     */
+    if (c->type == CHAN_X11 && c->u.x11.initial)
+        return;
+
     /*
      * If the remote end has a habit of ignoring maxpkt, limit the
      * window so that it has no choice (assuming it doesn't ignore the
@@ -6610,7 +7563,7 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
      */
     if (newwin / 2 >= c->v.v2.locwindow) {
        struct Packet *pktout;
-       struct winadj *wa;
+       unsigned *up;
 
        /*
         * In order to keep track of how much window the client
@@ -6621,33 +7574,15 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
         * This is only necessary if we're opening the window wide.
         * If we're not, then throughput is being constrained by
         * something other than the maximum window size anyway.
-        *
-        * We also only send this if the main channel has finished its
-        * initial CHANNEL_REQUESTs and installed the default
-        * CHANNEL_FAILURE handler, so as not to risk giving it
-        * unexpected CHANNEL_FAILUREs.
         */
        if (newwin == c->v.v2.locmaxwin &&
-           ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, c->remoteid);
-           ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
-           ssh2_pkt_addbool(pktout, TRUE);
+            !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+           up = snew(unsigned);
+           *up = newwin - c->v.v2.locwindow;
+           pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+                                      ssh2_handle_winadj_response, up);
            ssh2_pkt_send(ssh, pktout);
 
-           /*
-            * CHANNEL_FAILURE doesn't come with any indication of
-            * what message caused it, so we have to keep track of the
-            * outstanding CHANNEL_REQUESTs ourselves.
-            */
-           wa = snew(struct winadj);
-           wa->size = newwin - c->v.v2.locwindow;
-           wa->next = NULL;
-           if (!c->v.v2.winadj_head)
-               c->v.v2.winadj_head = wa;
-           else
-               c->v.v2.winadj_tail->next = wa;
-           c->v.v2.winadj_tail = wa;
            if (c->v.v2.throttle_state != UNTHROTTLED)
                c->v.v2.throttle_state = UNTHROTTLING;
        } else {
@@ -6674,7 +7609,8 @@ static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
 
     c = find234(ssh->channels, &localid, ssh_channelfind);
     if (!c ||
-       (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+       (c->type != CHAN_SHARING && c->halfopen &&
+         pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
         pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
        char *buf = dupprintf("Received %s for %s channel %u",
                              ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
@@ -6687,14 +7623,21 @@ static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
     return c;
 }
 
-static int ssh2_handle_winadj_response(struct ssh_channel *c)
+static void ssh2_handle_winadj_response(struct ssh_channel *c,
+                                       struct Packet *pktin, void *ctx)
 {
-    struct winadj *wa = c->v.v2.winadj_head;
-    if (!wa)
-       return FALSE;
-    c->v.v2.winadj_head = wa->next;
-    c->v.v2.remlocwin += wa->size;
-    sfree(wa);
+    unsigned *sizep = ctx;
+
+    /*
+     * Winadj responses should always be failures. However, at least
+     * one server ("boks_sshd") is known to return SUCCESS for channel
+     * requests it's never heard of, such as "winadj@putty". Raised
+     * with foxt.com as bug 090916-090424, but for the sake of a quiet
+     * life, we don't worry about what kind of response we got.
+     */
+
+    c->v.v2.remlocwin += *sizep;
+    sfree(sizep);
     /*
      * winadj messages are only sent when the window is fully open, so
      * if we get an ack of one, we know any pending unthrottle is
@@ -6702,56 +7645,33 @@ static int ssh2_handle_winadj_response(struct ssh_channel *c)
      */
     if (c->v.v2.throttle_state == UNTHROTTLING)
        c->v.v2.throttle_state = UNTHROTTLED;
-    /*
-     * We may now initiate channel-closing procedures, if that winadj
-     * was the last thing outstanding before we send CHANNEL_CLOSE.
-     */
-    ssh2_channel_check_close(c);
-    return TRUE;
 }
 
-static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
+static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
 {
-    /*
-     * This should never get called.  All channel requests are either
-     * sent with want_reply false, are sent before this handler gets
-     * installed, or are "winadj@putty" requests, which servers should
-     * never respond to with success.
-     *
-     * However, at least one server ("boks_sshd") is known to return
-     * SUCCESS for channel requests it's never heard of, such as
-     * "winadj@putty". Raised with foxt.com as bug 090916-090424, but
-     * for the sake of a quiet life, we handle it just the same as the
-     * expected FAILURE.
-     */
-    struct ssh_channel *c;
+    struct ssh_channel *c = ssh2_channel_msg(ssh, pktin);
+    struct outstanding_channel_request *ocr;
 
-    c = ssh2_channel_msg(ssh, pktin);
-    if (!c)
+    if (!c) return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
+    ocr = c->v.v2.chanreq_head;
+    if (!ocr) {
+       ssh2_msg_unexpected(ssh, pktin);
        return;
-    if (!ssh2_handle_winadj_response(c))
-       ssh_disconnect(ssh, NULL,
-                      "Received unsolicited SSH_MSG_CHANNEL_SUCCESS",
-                      SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
-}
-
-static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
-{
+    }
+    ocr->handler(c, pktin, ocr->ctx);
+    c->v.v2.chanreq_head = ocr->next;
+    sfree(ocr);
     /*
-     * The only time this should get called is for "winadj@putty"
-     * messages sent above.  All other channel requests are either
-     * sent with want_reply false or are sent before this handler gets
-     * installed.
+     * We may now initiate channel-closing procedures, if that
+     * CHANNEL_REQUEST was the last thing outstanding before we send
+     * CHANNEL_CLOSE.
      */
-    struct ssh_channel *c;
-
-    c = ssh2_channel_msg(ssh, pktin);
-    if (!c)
-       return;
-    if (!ssh2_handle_winadj_response(c))
-       ssh_disconnect(ssh, NULL,
-                      "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
-                      SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+    ssh2_channel_check_close(c);
 }
 
 static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
@@ -6760,6 +7680,11 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     if (!(c->closes & CLOSES_SENT_EOF)) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
        ssh2_try_send_and_unthrottle(ssh, c);
@@ -6774,6 +7699,11 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
        ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
        return;                        /* extended but not stderr */
@@ -6790,10 +7720,10 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
                             data, length);
            break;
          case CHAN_X11:
-           bufsize = x11_send(c->u.x11.s, data, length);
+           bufsize = x11_send(c->u.x11.xconn, data, length);
            break;
          case CHAN_SOCKDATA:
-           bufsize = pfd_send(c->u.pfd.s, data, length);
+           bufsize = pfd_send(c->u.pfd.pf, data, length);
            break;
          case CHAN_AGENT:
            while (length > 0) {
@@ -6826,6 +7756,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
                if (c->u.a.lensofar == c->u.a.totallen) {
                    void *reply;
                    int replylen;
+                    c->u.a.outstanding_requests++;
                    if (agent_query(c->u.a.message,
                                    c->u.a.totallen,
                                    &reply, &replylen,
@@ -6861,15 +7792,59 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
         * buffering anything at all and we're in "simple" mode,
         * throttle the whole channel.
         */
-       if ((bufsize > c->v.v2.locmaxwin ||
-            (conf_get_int(ssh->conf, CONF_ssh_simple) && bufsize > 0)) &&
-           !c->throttling_conn) {
+       if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0))
+            && !c->throttling_conn) {
            c->throttling_conn = 1;
            ssh_throttle_conn(ssh, +1);
        }
     }
 }
 
+static void ssh_check_termination(Ssh ssh)
+{
+    if (ssh->version == 2 &&
+        !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
+        count234(ssh->channels) == 0 &&
+        !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) {
+        /*
+         * We used to send SSH_MSG_DISCONNECT here, because I'd
+         * believed that _every_ conforming SSH-2 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.
+         */
+        ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
+    }
+}
+
+void ssh_sharing_downstream_connected(Ssh ssh, unsigned id)
+{
+    logeventf(ssh, "Connection sharing downstream #%u connected", id);
+}
+
+void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id)
+{
+    logeventf(ssh, "Connection sharing downstream #%u disconnected", id);
+    ssh_check_termination(ssh);
+}
+
+void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...)
+{
+    va_list ap;
+    char *buf;
+
+    va_start(ap, logfmt);
+    buf = dupvprintf(logfmt, ap);
+    va_end(ap);
+    if (id)
+        logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf);
+    else
+        logeventf(ssh, "Connection sharing: %s", buf);
+    sfree(buf);
+}
+
 static void ssh_channel_destroy(struct ssh_channel *c)
 {
     Ssh ssh = c->ssh;
@@ -6880,46 +7855,32 @@ static void ssh_channel_destroy(struct ssh_channel *c)
         update_specials_menu(ssh->frontend);
         break;
       case CHAN_X11:
-        if (c->u.x11.s != NULL)
-            x11_close(c->u.x11.s);
+        if (c->u.x11.xconn != NULL)
+            x11_close(c->u.x11.xconn);
         logevent("Forwarded X11 connection terminated");
         break;
       case CHAN_AGENT:
         sfree(c->u.a.message);
         break;
       case CHAN_SOCKDATA:
-        if (c->u.pfd.s != NULL)
-            pfd_close(c->u.pfd.s);
+        if (c->u.pfd.pf != NULL)
+            pfd_close(c->u.pfd.pf);
         logevent("Forwarded port closed");
         break;
     }
 
     del234(ssh->channels, c);
-    if (ssh->version == 2)
+    if (ssh->version == 2) {
         bufchain_clear(&c->v.v2.outbuffer);
+       assert(c->v.v2.chanreq_head == NULL);
+    }
     sfree(c);
 
     /*
-     * See if that was the last channel left open.
-     * (This is only our termination condition if we're
-     * not running in -N mode.)
+     * If that was the last channel left open, we might need to
+     * terminate.
      */
-    if (ssh->version == 2 &&
-        !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
-        count234(ssh->channels) == 0) {
-        /*
-         * We used to send SSH_MSG_DISCONNECT here,
-         * because I'd believed that _every_ conforming
-         * SSH-2 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.
-         */
-        ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
-    }
+    ssh_check_termination(ssh);
 }
 
 static void ssh2_channel_check_close(struct ssh_channel *c)
@@ -6927,21 +7888,33 @@ static void ssh2_channel_check_close(struct ssh_channel *c)
     Ssh ssh = c->ssh;
     struct Packet *pktout;
 
-    if ((c->closes & (CLOSES_SENT_EOF | CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
-        == (CLOSES_SENT_EOF | CLOSES_RCVD_EOF) && !c->v.v2.winadj_head) {
+    if (c->halfopen) {
         /*
-         * We have both sent and received EOF, and we have no
-         * outstanding winadj channel requests, which means the
-         * channel is in final wind-up. But we haven't sent CLOSE, so
-         * let's do so now.
+         * If we've sent out our own CHANNEL_OPEN but not yet seen
+         * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+         * it's too early to be sending close messages of any kind.
+         */
+        return;
+    }
+
+    if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) ||
+        c->type == CHAN_ZOMBIE) &&
+       !c->v.v2.chanreq_head &&
+       !(c->closes & CLOSES_SENT_CLOSE)) {
+        /*
+         * We have both sent and received EOF (or the channel is a
+         * zombie), and we have no outstanding channel requests, which
+         * means the channel is in final wind-up. But we haven't sent
+         * CLOSE, so let's do so now.
          */
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
        ssh2_pkt_adduint32(pktout, c->remoteid);
        ssh2_pkt_send(ssh, pktout);
-        c->closes |= CLOSES_SENT_CLOSE;
+        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
     }
 
     if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+       assert(c->v.v2.chanreq_head == NULL);
         /*
          * We have both sent and received CLOSE, which means we're
          * completely done with the channel.
@@ -6957,12 +7930,14 @@ static void ssh2_channel_got_eof(struct ssh_channel *c)
     c->closes |= CLOSES_RCVD_EOF;
 
     if (c->type == CHAN_X11) {
-       x11_send_eof(c->u.x11.s);
+       x11_send_eof(c->u.x11.xconn);
     } else if (c->type == CHAN_AGENT) {
-        /* Manufacture an outgoing EOF in response to the incoming one. */
-        sshfwd_write_eof(c);
+        if (c->u.a.outstanding_requests == 0) {
+            /* Manufacture an outgoing EOF in response to the incoming one. */
+            sshfwd_write_eof(c);
+        }
     } else if (c->type == CHAN_SOCKDATA) {
-       pfd_send_eof(c->u.pfd.s);
+       pfd_send_eof(c->u.pfd.pf);
     } else if (c->type == CHAN_MAINSESSION) {
         Ssh ssh = c->ssh;
 
@@ -6991,6 +7966,11 @@ static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     ssh2_channel_got_eof(c);
 }
 
@@ -7001,6 +7981,11 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
 
     /*
      * When we receive CLOSE on a channel, we assume it comes with an
@@ -7008,6 +7993,21 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
      */
     ssh2_channel_got_eof(c);
 
+    if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
+        /*
+         * It also means we stop expecting to see replies to any
+         * outstanding channel requests, so clean those up too.
+         * (ssh_chanreq_init will enforce by assertion that we don't
+         * subsequently put anything back on this list.)
+         */
+        while (c->v.v2.chanreq_head) {
+            struct outstanding_channel_request *ocr = c->v.v2.chanreq_head;
+            ocr->handler(c, NULL, ocr->ctx);
+            c->v.v2.chanreq_head = ocr->next;
+            sfree(ocr);
+        }
+    }
+
     /*
      * And we also send an outgoing EOF, if we haven't already, on the
      * assumption that CLOSE is a pretty forceful announcement that
@@ -7025,13 +8025,22 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
             ssh->send_ok = 0;     /* stop trying to read from stdin */
             break;
           case CHAN_X11:
-           x11_override_throttle(c->u.x11.s, 1);
+           x11_override_throttle(c->u.x11.xconn, 1);
            break;
          case CHAN_SOCKDATA:
-           pfd_override_throttle(c->u.pfd.s, 1);
+           pfd_override_throttle(c->u.pfd.pf, 1);
            break;
         }
 
+        /*
+         * Abandon any buffered data we still wanted to send to this
+         * channel. Receiving a CHANNEL_CLOSE is an indication that
+         * the server really wants to get on and _destroy_ this
+         * channel, and it isn't going to send us any further
+         * WINDOW_ADJUSTs to permit us to send pending stuff.
+         */
+        bufchain_clear(&c->v.v2.outbuffer);
+
         /*
          * Send outgoing EOF.
          */
@@ -7054,17 +8063,47 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
-    if (c->type != CHAN_SOCKDATA_DORMANT)
-       return;                        /* dunno why they're confirming this */
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
+    assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
     c->remoteid = ssh_pkt_getuint32(pktin);
     c->halfopen = FALSE;
-    c->type = CHAN_SOCKDATA;
     c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
     c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
-    if (c->u.pfd.s)
-       pfd_confirm(c->u.pfd.s);
+
+    if (c->type == CHAN_SOCKDATA_DORMANT) {
+        c->type = CHAN_SOCKDATA;
+        if (c->u.pfd.pf)
+            pfd_confirm(c->u.pfd.pf);
+    } else if (c->type == CHAN_ZOMBIE) {
+        /*
+         * This case can occur if a local socket error occurred
+         * between us sending out CHANNEL_OPEN and receiving
+         * OPEN_CONFIRMATION. In this case, all we can do is
+         * immediately initiate close proceedings now that we know the
+         * server's id to put in the close message.
+         */
+        ssh2_channel_check_close(c);
+    } else {
+        /*
+         * We never expect to receive OPEN_CONFIRMATION for any
+         * *other* channel type (since only local-to-remote port
+         * forwardings cause us to send CHANNEL_OPEN after the main
+         * channel is live - all other auxiliary channel types are
+         * initiated from the server end). It's safe to enforce this
+         * by assertion rather than by ssh_disconnect, because the
+         * real point is that we never constructed a half-open channel
+         * structure in the first place with any type other than the
+         * above.
+         */
+        assert(!"Funny channel type in ssh2_msg_channel_open_confirmation");
+    }
+
     if (c->pending_eof)
-        ssh_channel_try_eof(c);
+        ssh_channel_try_eof(c);        /* in case we had a pending EOF */
 }
 
 static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
@@ -7080,20 +8119,46 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
     char *reason_string;
     int reason_length;
     struct ssh_channel *c;
+
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
-    if (c->type != CHAN_SOCKDATA_DORMANT)
-       return;                        /* dunno why they're failing this */
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
+    assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
 
-    reason_code = ssh_pkt_getuint32(pktin);
-    if (reason_code >= lenof(reasons))
-       reason_code = 0; /* ensure reasons[reason_code] in range */
-    ssh_pkt_getstring(pktin, &reason_string, &reason_length);
-    logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
-             reasons[reason_code], reason_length, reason_string);
+    if (c->type == CHAN_SOCKDATA_DORMANT) {
+        reason_code = ssh_pkt_getuint32(pktin);
+        if (reason_code >= lenof(reasons))
+            reason_code = 0; /* ensure reasons[reason_code] in range */
+        ssh_pkt_getstring(pktin, &reason_string, &reason_length);
+        logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
+                  reasons[reason_code], reason_length, reason_string);
 
-    pfd_close(c->u.pfd.s);
+        pfd_close(c->u.pfd.pf);
+    } else if (c->type == CHAN_ZOMBIE) {
+        /*
+         * This case can occur if a local socket error occurred
+         * between us sending out CHANNEL_OPEN and receiving
+         * OPEN_FAILURE. In this case, we need do nothing except allow
+         * the code below to throw the half-open channel away.
+         */
+    } else {
+        /*
+         * We never expect to receive OPEN_FAILURE for any *other*
+         * channel type (since only local-to-remote port forwardings
+         * cause us to send CHANNEL_OPEN after the main channel is
+         * live - all other auxiliary channel types are initiated from
+         * the server end). It's safe to enforce this by assertion
+         * rather than by ssh_disconnect, because the real point is
+         * that we never constructed a half-open channel structure in
+         * the first place with any type other than the above.
+         */
+        assert(!"Funny channel type in ssh2_msg_channel_open_failure");
+    }
 
     del234(ssh->channels, c);
     sfree(c);
@@ -7110,9 +8175,24 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
     c = ssh2_channel_msg(ssh, pktin);
     if (!c)
        return;
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return;
+    }
     ssh_pkt_getstring(pktin, &type, &typelen);
     want_reply = ssh2_pkt_getbool(pktin);
 
+    if (c->closes & CLOSES_SENT_CLOSE) {
+        /*
+         * We don't reply to channel requests after we've sent
+         * CHANNEL_CLOSE for the channel, because our reply might
+         * cross in the network with the other side's CHANNEL_CLOSE
+         * and arrive after they have wound the channel up completely.
+         */
+        want_reply = FALSE;
+    }
+
     /*
      * Having got the channel number, we now look at
      * the request type string to see if it's something
@@ -7153,16 +8233,18 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                    is_int = FALSE;
                } else {
                    int maybe_int = FALSE, maybe_str = FALSE;
-#define CHECK_HYPOTHESIS(offset, result) \
-    do { \
-       long q = offset; \
-       if (q >= 0 && q+4 <= len) { \
-           q = q + 4 + GET_32BIT(p+q); \
-           if (q >= 0 && q+4 <= len && \
-                   ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \
-               result = TRUE; \
-       } \
-    } while(0)
+#define CHECK_HYPOTHESIS(offset, result)                                \
+                    do                                                  \
+                    {                                                   \
+                        int q = toint(offset);                          \
+                        if (q >= 0 && q+4 <= len) {                     \
+                            q = toint(q + 4 + GET_32BIT(p+q));          \
+                            if (q >= 0 && q+4 <= len &&                 \
+                                ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \
+                                q == len)                               \
+                                result = TRUE;                          \
+                        }                                               \
+                    } while(0)
                    CHECK_HYPOTHESIS(4+1, maybe_int);
                    CHECK_HYPOTHESIS(4+num+1, maybe_str);
 #undef CHECK_HYPOTHESIS
@@ -7300,6 +8382,30 @@ static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
     }
 }
 
+struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
+                                                void *share_cs,
+                                                void *share_chan)
+{
+    struct X11FakeAuth *auth;
+
+    /*
+     * Make up a new set of fake X11 auth data, and add it to the tree
+     * of currently valid ones with an indication of the sharing
+     * context that it's relevant to.
+     */
+    auth = x11_invent_fake_auth(ssh->x11authtree, authtype);
+    auth->share_cs = share_cs;
+    auth->share_chan = share_chan;
+
+    return auth;
+}
+
+void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth)
+{
+    del234(ssh->x11authtree, auth);
+    x11_free_fake_auth(auth);
+}
+
 static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 {
     char *type;
@@ -7310,6 +8416,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     char *error = NULL;
     struct ssh_channel *c;
     unsigned remid, winsize, pktsize;
+    unsigned our_winsize_override = 0;
     struct Packet *pktout;
 
     ssh_pkt_getstring(pktin, &type, &typelen);
@@ -7322,7 +8429,6 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 
     if (typelen == 3 && !memcmp(type, "x11", 3)) {
        char *addrstr;
-       const char *x11err;
 
        ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
        addrstr = snewn(peeraddrlen+1, char);
@@ -7333,42 +8439,69 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        logeventf(ssh, "Received X11 connect request from %s:%d",
                  addrstr, peerport);
 
-       if (!ssh->X11_fwd_enabled)
+       if (!ssh->X11_fwd_enabled && !ssh->connshare)
            error = "X11 forwarding is not enabled";
-       else if ((x11err = x11_init(&c->u.x11.s, ssh->x11disp, c,
-                                   addrstr, peerport, ssh->conf)) != NULL) {
-           logeventf(ssh, "Local X11 connection failed: %s", x11err);
-           error = "Unable to open an X11 connection";
-       } else {
-           logevent("Opening X11 forward connection succeeded");
+       else {
+            c->u.x11.xconn = x11_init(ssh->x11authtree, c,
+                                      addrstr, peerport);
            c->type = CHAN_X11;
+            c->u.x11.initial = TRUE;
+
+            /*
+             * If we are a connection-sharing upstream, then we should
+             * initially present a very small window, adequate to take
+             * the X11 initial authorisation packet but not much more.
+             * Downstream will then present us a larger window (by
+             * fiat of the connection-sharing protocol) and we can
+             * guarantee to send a positive-valued WINDOW_ADJUST.
+             */
+            if (ssh->connshare)
+                our_winsize_override = 128;
+
+            logevent("Opened X11 forward channel");
        }
 
        sfree(addrstr);
     } else if (typelen == 15 &&
               !memcmp(type, "forwarded-tcpip", 15)) {
        struct ssh_rportfwd pf, *realpf;
-       char *dummy;
-       int dummylen;
-       ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */
+       char *shost;
+       int shostlen;
+       ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
+        pf.shost = dupprintf("%.*s", shostlen, shost);
        pf.sport = ssh_pkt_getuint32(pktin);
        ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
        peerport = ssh_pkt_getuint32(pktin);
        realpf = find234(ssh->rportfwds, &pf, NULL);
-       logeventf(ssh, "Received remote port %d open request "
-                 "from %s:%d", pf.sport, peeraddr, peerport);
+       logeventf(ssh, "Received remote port %s:%d open request "
+                 "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+        sfree(pf.shost);
+
        if (realpf == NULL) {
            error = "Remote port is not recognised";
        } else {
-           const char *e = pfd_newconnect(&c->u.pfd.s,
-                                          realpf->dhost,
-                                          realpf->dport, c,
-                                          ssh->conf,
-                                          realpf->pfrec->addressfamily);
+            char *err;
+
+            if (realpf->share_ctx) {
+                /*
+                 * This port forwarding is on behalf of a
+                 * connection-sharing downstream, so abandon our own
+                 * channel-open procedure and just pass the message on
+                 * to sshshare.c.
+                 */
+                share_got_pkt_from_server(realpf->share_ctx, pktin->type,
+                                          pktin->body, pktin->length);
+                sfree(c);
+                return;
+            }
+
+            err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport,
+                              c, ssh->conf, realpf->pfrec->addressfamily);
            logeventf(ssh, "Attempting to forward remote port to "
                      "%s:%d", realpf->dhost, realpf->dport);
-           if (e != NULL) {
-               logeventf(ssh, "Port open failed: %s", e);
+           if (err != NULL) {
+               logeventf(ssh, "Port open failed: %s", err);
+                sfree(err);
                error = "Port open failed";
            } else {
                logevent("Forwarded port opened successfully");
@@ -7382,6 +8515,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        else {
            c->type = CHAN_AGENT;       /* identify channel type */
            c->u.a.lensofar = 0;
+            c->u.a.message = NULL;
+            c->u.a.outstanding_requests = 0;
        }
     } else {
        error = "Unsupported channel type requested";
@@ -7402,6 +8537,10 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        ssh2_channel_init(c);
        c->v.v2.remwindow = winsize;
        c->v.v2.remmaxpkt = pktsize;
+        if (our_winsize_override) {
+            c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+                our_winsize_override;
+        }
        add234(ssh->channels, c);
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
        ssh2_pkt_adduint32(pktout, c->remoteid);
@@ -7412,6 +8551,43 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     }
 }
 
+void sshfwd_x11_sharing_handover(struct ssh_channel *c,
+                                 void *share_cs, void *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len)
+{
+    /*
+     * This function is called when we've just discovered that an X
+     * forwarding channel on which we'd been handling the initial auth
+     * ourselves turns out to be destined for a connection-sharing
+     * downstream. So we turn the channel into a CHAN_SHARING, meaning
+     * that we completely stop tracking windows and buffering data and
+     * just pass more or less unmodified SSH messages back and forth.
+     */
+    c->type = CHAN_SHARING;
+    c->u.sharing.ctx = share_cs;
+    share_setup_x11_channel(share_cs, share_chan,
+                            c->localid, c->remoteid, c->v.v2.remwindow,
+                            c->v.v2.remmaxpkt, c->v.v2.locwindow,
+                            peer_addr, peer_port, endian,
+                            protomajor, protominor,
+                            initial_data, initial_len);
+}
+
+void sshfwd_x11_is_local(struct ssh_channel *c)
+{
+    /*
+     * This function is called when we've just discovered that an X
+     * forwarding channel is _not_ destined for a connection-sharing
+     * downstream but we're going to handle it ourselves. We stop
+     * presenting a cautiously small window and go into ordinary data
+     * exchange mode.
+     */
+    c->u.x11.initial = FALSE;
+    ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
 /*
  * Buffer banner messages for later display at some convenient point,
  * if we're going to display them.
@@ -7449,13 +8625,212 @@ static void ssh2_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_adduint32(pktout, arg);
 }
 
+static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin,
+                           void *ctx)
+{
+    struct ssh2_setup_x11_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_x11_state, ctx);
+
+    crBeginState;
+
+    logevent("Requesting X11 forwarding");
+    pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req",
+                               ssh2_setup_x11, s);
+    ssh2_pkt_addbool(pktout, 0);              /* many connections */
+    ssh2_pkt_addstring(pktout, ssh->x11auth->protoname);
+    ssh2_pkt_addstring(pktout, ssh->x11auth->datastring);
+    ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
+    ssh2_pkt_send(ssh, pktout);
+
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
+
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("X11 forwarding enabled");
+            ssh->X11_fwd_enabled = TRUE;
+        } else
+            logevent("X11 forwarding refused");
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin,
+                                  void *ctx)
+{
+    struct ssh2_setup_agent_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_agent_state, ctx);
+
+    crBeginState;
+
+    logevent("Requesting OpenSSH-style agent forwarding");
+    pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com",
+                               ssh2_setup_agent, s);
+    ssh2_pkt_send(ssh, pktout);
+
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
+
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logevent("Agent forwarding enabled");
+            ssh->agentfwd_enabled = TRUE;
+        } else
+            logevent("Agent forwarding refused");
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin,
+                                void *ctx)
+{
+    struct ssh2_setup_pty_state {
+       int crLine;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_pty_state, ctx);
+
+    crBeginState;
+
+    /* Unpick the terminal-speed string. */
+    /* XXX perhaps we should allow no speeds to be sent. */
+    ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
+    sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
+    /* Build the pty request. */
+    pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req",
+                               ssh2_setup_pty, s);
+    ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
+    ssh2_pkt_adduint32(pktout, ssh->term_width);
+    ssh2_pkt_adduint32(pktout, ssh->term_height);
+    ssh2_pkt_adduint32(pktout, 0);            /* pixel width */
+    ssh2_pkt_adduint32(pktout, 0);            /* pixel height */
+    ssh2_pkt_addstring_start(pktout);
+    parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout);
+    ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED);
+    ssh2_pkt_adduint32(pktout, ssh->ispeed);
+    ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED);
+    ssh2_pkt_adduint32(pktout, ssh->ospeed);
+    ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */
+    ssh2_pkt_send(ssh, pktout);
+    ssh->state = SSH_STATE_INTERMED;
+
+    /* Wait to be called back with either a response packet, or NULL
+     * meaning clean up and free our data */
+    crReturnV;
+
+    if (pktin) {
+        if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+            logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+                      ssh->ospeed, ssh->ispeed);
+            ssh->got_pty = TRUE;
+        } else {
+            c_write_str(ssh, "Server refused to allocate pty\r\n");
+            ssh->editing = ssh->echoing = 1;
+        }
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
+                          void *ctx)
+{
+    struct ssh2_setup_env_state {
+       int crLine;
+       int num_env, env_left, env_ok;
+    };
+    Ssh ssh = c->ssh;
+    struct Packet *pktout;
+    crStateP(ssh2_setup_env_state, ctx);
+
+    crBeginState;
+
+    /*
+     * Send environment variables.
+     * 
+     * Simplest thing here is to send all the requests at once, and
+     * then wait for a whole bunch of successes or failures.
+     */
+    s->num_env = 0;
+    {
+       char *key, *val;
+
+       for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
+            val != NULL;
+            val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
+           pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
+           ssh2_pkt_addstring(pktout, key);
+           ssh2_pkt_addstring(pktout, val);
+           ssh2_pkt_send(ssh, pktout);
+
+           s->num_env++;
+       }
+       if (s->num_env)
+           logeventf(ssh, "Sent %d environment variables", s->num_env);
+    }
+
+    if (s->num_env) {
+       s->env_ok = 0;
+       s->env_left = s->num_env;
+
+       while (s->env_left > 0) {
+            /* Wait to be called back with either a response packet,
+             * or NULL meaning clean up and free our data */
+            crReturnV;
+           if (!pktin) goto out;
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
+               s->env_ok++;
+           s->env_left--;
+       }
+
+       if (s->env_ok == s->num_env) {
+           logevent("All environment variables successfully set");
+       } else if (s->env_ok == 0) {
+           logevent("All environment variables refused");
+           c_write_str(ssh, "Server refused to set environment variables\r\n");
+       } else {
+           logeventf(ssh, "%d environment variables refused",
+                     s->num_env - s->env_ok);
+           c_write_str(ssh, "Server refused to set all environment variables\r\n");
+       }
+    }
+  out:;
+    crFinishFreeV;
+}
+
 /*
  * Handle the SSH-2 userauth and connection layers.
  */
+static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin)
+{
+    do_ssh2_authconn(ssh, NULL, 0, pktin);
+}
+
+static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
+                                  void *ctx)
+{
+    if (pktin)
+        do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+}
+
 static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                             struct Packet *pktin)
 {
     struct do_ssh2_authconn_state {
+       int crLine;
        enum {
            AUTH_TYPE_NONE,
                AUTH_TYPE_PUBLICKEY,
@@ -7494,7 +8869,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int siglen, retlen, len;
        char *q, *agentreq, *ret;
        int try_send;
-       int num_env, env_left, env_ok;
        struct Packet *pktout;
        Filename *keyfile;
 #ifndef NO_GSSAPI
@@ -7506,41 +8880,68 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        Ssh_gss_stat gss_stat;
 #endif
     };
-    crState(do_ssh2_authconn_state);
-
-    crBegin(ssh->do_ssh2_authconn_crstate);
-
-    s->done_service_req = FALSE;
-    s->we_are_in = s->userauth_success = FALSE;
-#ifndef NO_GSSAPI
-    s->tried_gssapi = FALSE;
-#endif
-
-    if (!conf_get_int(ssh->conf, CONF_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;
-       }
+    crState(do_ssh2_authconn_state);
+
+    crBeginState;
+
+    /* Register as a handler for all the messages this coroutine handles. */
+    ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn;
+    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */
+    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn;
+    
+    s->done_service_req = FALSE;
+    s->we_are_in = s->userauth_success = FALSE;
+    s->agent_response = NULL;
+#ifndef NO_GSSAPI
+    s->tried_gssapi = FALSE;
+#endif
+
+    if (!ssh->bare_connection) {
+        if (!conf_get_int(ssh->conf, CONF_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;
+            }
+        }
+    } else {
+        s->we_are_in = TRUE;
     }
 
     /* Arrange to be able to deal with any BANNERs that come in.
@@ -7635,13 +9036,53 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                int keyi;
                unsigned char *p;
                p = s->agent_response + 5;
-               s->nkeys = GET_32BIT(p);
+               s->nkeys = toint(GET_32BIT(p));
+
+                /*
+                 * Vet the Pageant response to ensure that the key
+                 * count and blob lengths make sense.
+                 */
+                if (s->nkeys < 0) {
+                    logeventf(ssh, "Pageant response contained a negative"
+                              " key count %d", s->nkeys);
+                    s->nkeys = 0;
+                    goto done_agent_query;
+                } else {
+                    unsigned char *q = p + 4;
+                    int lenleft = s->agent_responselen - 5 - 4;
+
+                    for (keyi = 0; keyi < s->nkeys; keyi++) {
+                        int bloblen, commentlen;
+                        if (lenleft < 4) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        bloblen = toint(GET_32BIT(q));
+                        if (bloblen < 0 || bloblen > lenleft) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        lenleft -= 4 + bloblen;
+                        q += 4 + bloblen;
+                        commentlen = toint(GET_32BIT(q));
+                        if (commentlen < 0 || commentlen > lenleft) {
+                            logeventf(ssh, "Pageant response was truncated");
+                            s->nkeys = 0;
+                            goto done_agent_query;
+                        }
+                        lenleft -= 4 + commentlen;
+                        q += 4 + commentlen;
+                    }
+                }
+
                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);
+                       s->pklen = toint(GET_32BIT(p));
                        if (s->pklen == s->publickey_bloblen &&
                            !memcmp(p+4, s->publickey_blob,
                                    s->publickey_bloblen)) {
@@ -7652,7 +9093,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                            break;
                        }
                        p += 4 + s->pklen;
-                       p += GET_32BIT(p) + 4; /* comment */
+                       p += toint(GET_32BIT(p)) + 4; /* comment */
                    }
                    if (!s->pkblob_in_agent) {
                        logevent("Configured key file not in Pageant");
@@ -7662,6 +9103,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            } else {
                 logevent("Failed to get reply from Pageant");
            }
+          done_agent_query:;
        }
 
     }
@@ -7913,13 +9355,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                logeventf(ssh, "Trying Pageant key #%d", s->keyi);
 
                /* Unpack key from agent response */
-               s->pklen = GET_32BIT(s->agentp);
+               s->pklen = toint(GET_32BIT(s->agentp));
                s->agentp += 4;
                s->pkblob = (char *)s->agentp;
                s->agentp += s->pklen;
-               s->alglen = GET_32BIT(s->pkblob);
+               s->alglen = toint(GET_32BIT(s->pkblob));
                s->alg = s->pkblob + 4;
-               s->commentlen = GET_32BIT(s->agentp);
+               s->commentlen = toint(GET_32BIT(s->agentp));
                s->agentp += 4;
                s->commentp = (char *)s->agentp;
                s->agentp += s->commentlen;
@@ -8023,7 +9465,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    s->ret = vret;
                    sfree(s->agentreq);
                    if (s->ret) {
-                       if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+                       if (s->retlen >= 9 &&
+                            s->ret[4] == SSH2_AGENT_SIGN_RESPONSE &&
+                            GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) {
                            logevent("Sending Pageant's response");
                            ssh2_add_sigblob(ssh, s->pktout,
                                             s->pkblob, s->pklen,
@@ -8144,7 +9588,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    key = ssh2_load_userkey(s->keyfile, passphrase, &error);
                    if (passphrase) {
                        /* burn the evidence */
-                       memset(passphrase, 0, strlen(passphrase));
+                       smemclr(passphrase, strlen(passphrase));
                        sfree(passphrase);
                    }
                    if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
@@ -8225,6 +9669,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     logevent("Sent public key signature");
                    s->type = AUTH_TYPE_PUBLICKEY;
                    key->alg->freekey(key->data);
+                    sfree(key->comment);
+                    sfree(key);
                }
 
 #ifndef NO_GSSAPI
@@ -8553,10 +9999,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    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);
 
@@ -8639,9 +10083,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                                                        /* 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);
-               end_log_omission(ssh, s->pktout);
                ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
                logevent("Sent password");
                s->type = AUTH_TYPE_PASSWORD;
@@ -8723,7 +10165,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                             */
                            /* burn the evidence */
                            free_prompts(s->cur_prompt);
-                           memset(s->password, 0, strlen(s->password));
+                           smemclr(s->password, strlen(s->password));
                            sfree(s->password);
                            ssh_disconnect(ssh, NULL, "Unable to authenticate",
                                           SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
@@ -8739,7 +10181,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                         * 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));
+                           smemclr(s->password, strlen(s->password));
                                /* burn the evidence */
                            sfree(s->password);
                            s->password =
@@ -8768,12 +10210,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                                                        /* 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);
                    ssh2_pkt_addstring(s->pktout,
                                       s->cur_prompt->prompts[1]->result);
                    free_prompts(s->cur_prompt);
-                   end_log_omission(ssh, s->pktout);
                    ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
                    logevent("Sent new password");
                    
@@ -8806,7 +10246,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                 * We don't need the old password any more, in any
                 * case. Burn the evidence.
                 */
-               memset(s->password, 0, strlen(s->password));
+               smemclr(s->password, strlen(s->password));
                sfree(s->password);
 
            } else {
@@ -8836,7 +10276,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     if (s->agent_response)
        sfree(s->agent_response);
 
-    if (s->userauth_success) {
+    if (s->userauth_success && !ssh->bare_connection) {
        /*
         * We've just received USERAUTH_SUCCESS, and we haven't sent any
         * packets since. Signal the transport layer to consider enacting
@@ -8850,10 +10290,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL);
     }
 
-    /*
-     * Now the connection protocol has started, one way or another.
-     */
-
     ssh->channels = newtree234(ssh_channelcmp);
 
     /*
@@ -8870,66 +10306,30 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) {
        ssh->mainchan = NULL;
-    } else if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
-       /*
-        * Just start a direct-tcpip channel and use it as the main
-        * channel.
-        */
+    } else {
        ssh->mainchan = snew(struct ssh_channel);
        ssh->mainchan->ssh = ssh;
        ssh2_channel_init(ssh->mainchan);
-       logeventf(ssh,
-                 "Opening direct-tcpip channel to %s:%d in place of session",
-                 conf_get_str(ssh->conf, CONF_ssh_nc_host),
-                 conf_get_int(ssh->conf, CONF_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);
-       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, conf_get_str(ssh->conf, CONF_ssh_nc_host));
-       ssh2_pkt_adduint32(s->pktout, conf_get_int(ssh->conf, CONF_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;
+       if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
+           /*
+            * Just start a direct-tcpip channel and use it as the main
+            * channel.
+            */
+           ssh_send_port_open(ssh->mainchan,
+                              conf_get_str(ssh->conf, CONF_ssh_nc_host),
+                              conf_get_int(ssh->conf, CONF_ssh_nc_port),
+                              "main channel");
+           ssh->ncmode = TRUE;
+       } else {
+           s->pktout = ssh2_chanopen_init(ssh->mainchan, "session");
+           logevent("Opening session as main channel");
+           ssh2_pkt_send(ssh, s->pktout);
+           ssh->ncmode = FALSE;
        }
-       ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
-       ssh->mainchan->halfopen = FALSE;
-       ssh->mainchan->type = CHAN_MAINSESSION;
-       ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
-       ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
-       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;
-       ssh2_channel_init(ssh->mainchan);
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
-       ssh2_pkt_addstring(s->pktout, "session");
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
-       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_send(ssh, s->pktout);
        crWaitUntilV(pktin);
        if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-           bombout(("Server refused to open a session"));
+           bombout(("Server refused to open channel"));
            crStopV;
            /* FIXME: error data comes back in FAILURE packet */
        }
@@ -8944,8 +10344,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
        add234(ssh->channels, ssh->mainchan);
        update_specials_menu(ssh->frontend);
-       logevent("Opened channel for session");
-       ssh->ncmode = FALSE;
+       logevent("Opened main channel");
     }
 
     /*
@@ -8965,259 +10364,131 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_msg_channel_request;
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
        ssh2_msg_channel_open;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
+
+    /*
+     * Now the connection protocol is properly up and running, with
+     * all those dispatch table entries, so it's safe to let
+     * downstreams start trying to open extra channels through us.
+     */
+    if (ssh->connshare)
+        share_activate(ssh->connshare, ssh->v_s);
 
-    if (ssh->mainchan && conf_get_int(ssh->conf, CONF_ssh_simple)) {
+    if (ssh->mainchan && ssh_is_simple(ssh)) {
        /*
         * This message indicates to the server that we promise
         * not to try to run any other channel in parallel with
         * this one, so it's safe for it to advertise a very large
         * window and leave the flow control to TCP.
         */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
-       ssh2_pkt_addbool(s->pktout, 0); /* no reply */
+       s->pktout = ssh2_chanreq_init(ssh->mainchan,
+                                     "simple@putty.projects.tartarus.org",
+                                     NULL, NULL);
        ssh2_pkt_send(ssh, s->pktout);
     }
 
-    /*
-     * Potentially enable X11 forwarding.
-     */
-    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
-       (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
-                                         conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
-       logevent("Requesting X11 forwarding");
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "x11-req");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_addbool(s->pktout, 0);        /* many connections */
-       ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname);
-       /*
-        * 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, ssh->x11disp->remoteauthdatastring);
-       end_log_omission(ssh, s->pktout);
-       ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum);
-       ssh2_pkt_send(ssh, s->pktout);
-
-       crWaitUntilV(pktin);
-
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to X11 forwarding request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
-       }
-    }
-
     /*
      * Enable port forwardings.
      */
     ssh_setup_portfwd(ssh, ssh->conf);
 
-    /*
-     * Potentially enable agent forwarding.
-     */
-    if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_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);
-       ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_send(ssh, s->pktout);
-
-       crWaitUntilV(pktin);
-
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to agent forwarding request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           logevent("Agent forwarding refused");
-       } else {
-           logevent("Agent forwarding enabled");
-           ssh->agentfwd_enabled = TRUE;
-       }
-    }
-
-    /*
-     * Now allocate a pty for the session.
-     */
-    if (ssh->mainchan && !ssh->ncmode && !conf_get_int(ssh->conf, CONF_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 */
-       sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
-       /* Build the pty request. */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
-       ssh2_pkt_addstring(s->pktout, "pty-req");
-       ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
-       ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_termtype));
-       ssh2_pkt_adduint32(s->pktout, ssh->term_width);
-       ssh2_pkt_adduint32(s->pktout, ssh->term_height);
-       ssh2_pkt_adduint32(s->pktout, 0);              /* pixel width */
-       ssh2_pkt_adduint32(s->pktout, 0);              /* pixel height */
-       ssh2_pkt_addstring_start(s->pktout);
-       parse_ttymodes(ssh, ssh2_send_ttymode, (void *)s->pktout);
-       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED);
-       ssh2_pkt_adduint32(s->pktout, ssh->ispeed);
-       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED);
-       ssh2_pkt_adduint32(s->pktout, ssh->ospeed);
-       ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
-       ssh2_pkt_send(ssh, s->pktout);
-       ssh->state = SSH_STATE_INTERMED;
-
-       crWaitUntilV(pktin);
-
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to pty request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           c_write_str(ssh, "Server refused to allocate pty\r\n");
-           ssh->editing = ssh->echoing = 1;
-       } else {
-           logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
-                     ssh->ospeed, ssh->ispeed);
-            ssh->got_pty = TRUE;
-       }
-    } else {
-       ssh->editing = ssh->echoing = 1;
-    }
-
-    /*
-     * Send environment variables.
-     * 
-     * 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->ncmode) {
-       char *key, *val;
-
-       s->num_env = 0;
-
-       for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
-            val != NULL;
-            val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
-           s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(s->pktout, "env");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, key);
-           ssh2_pkt_addstring(s->pktout, val);
-           ssh2_pkt_send(ssh, s->pktout);
+       /*
+        * Send the CHANNEL_REQUESTS for the main session channel.
+        * Each one is handled by its own little asynchronous
+        * co-routine.
+        */
 
-           s->num_env++;
-       }
+       /* Potentially enable X11 forwarding. */
+       if (conf_get_int(ssh->conf, CONF_x11_forward)) {
+            ssh->x11disp =
+                x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+                                  ssh->conf);
+            if (!ssh->x11disp) {
+                /* FIXME: return an error message from x11_setup_display */
+                logevent("X11 forwarding not enabled: unable to"
+                         " initialise X display");
+            } else {
+                ssh->x11auth = x11_invent_fake_auth
+                    (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth));
+                ssh->x11auth->disp = ssh->x11disp;
+
+                ssh2_setup_x11(ssh->mainchan, NULL, NULL);
+            }
+        }
 
-       if (s->num_env) {
-           logeventf(ssh, "Sent %d environment variables", s->num_env);
+       /* Potentially enable agent forwarding. */
+       if (ssh_agent_forwarding_permitted(ssh))
+           ssh2_setup_agent(ssh->mainchan, NULL, NULL);
 
-           s->env_ok = 0;
-           s->env_left = s->num_env;
+       /* Now allocate a pty for the session. */
+       if (!conf_get_int(ssh->conf, CONF_nopty))
+           ssh2_setup_pty(ssh->mainchan, NULL, NULL);
 
-           while (s->env_left > 0) {
-               crWaitUntilV(pktin);
+       /* Send environment variables. */
+       ssh2_setup_env(ssh->mainchan, NULL, NULL);
 
-               if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-                   if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-                       bombout(("Unexpected response to environment request:"
-                                " packet type %d", pktin->type));
-                       crStopV;
-                   }
-               } else {
-                   s->env_ok++;
-               }
+       /*
+        * Start a shell or a remote command. We may have to attempt
+        * this twice if the config data has provided a second choice
+        * of command.
+        */
+       while (1) {
+           int subsys;
+           char *cmd;
 
-               s->env_left--;
+           if (ssh->fallback_cmd) {
+               subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
+               cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
+           } else {
+               subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
+               cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
            }
 
-           if (s->env_ok == s->num_env) {
-               logevent("All environment variables successfully set");
-           } else if (s->env_ok == 0) {
-               logevent("All environment variables refused");
-               c_write_str(ssh, "Server refused to set environment variables\r\n");
+           if (subsys) {
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+                                             ssh2_response_authconn, NULL);
+               ssh2_pkt_addstring(s->pktout, cmd);
+           } else if (*cmd) {
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+                                             ssh2_response_authconn, NULL);
+               ssh2_pkt_addstring(s->pktout, cmd);
            } else {
-               logeventf(ssh, "%d environment variables refused",
-                         s->num_env - s->env_ok);
-               c_write_str(ssh, "Server refused to set all environment variables\r\n");
+               s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+                                             ssh2_response_authconn, NULL);
            }
-       }
-    }
-
-    /*
-     * Start a shell or a remote command. We may have to attempt
-     * this twice if the config data has provided a second choice
-     * of command.
-     */
-    if (ssh->mainchan && !ssh->ncmode) while (1) {
-       int subsys;
-       char *cmd;
-
-       if (ssh->fallback_cmd) {
-           subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
-           cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
-       } else {
-           subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
-           cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
-       }
-
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
-       if (subsys) {
-           ssh2_pkt_addstring(s->pktout, "subsystem");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, cmd);
-       } else if (*cmd) {
-           ssh2_pkt_addstring(s->pktout, "exec");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-           ssh2_pkt_addstring(s->pktout, cmd);
-       } else {
-           ssh2_pkt_addstring(s->pktout, "shell");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
-       }
-       ssh2_pkt_send(ssh, s->pktout);
+           ssh2_pkt_send(ssh, s->pktout);
 
-       crWaitUntilV(pktin);
+           crWaitUntilV(pktin);
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to shell/command request:"
-                        " packet type %d", pktin->type));
+           if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
+               if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
+                   bombout(("Unexpected response to shell/command request:"
+                            " packet type %d", pktin->type));
+                   crStopV;
+               }
+               /*
+                * We failed to start the command. If this is the
+                * fallback command, we really are finished; if it's
+                * not, and if the fallback command exists, try falling
+                * back to it before complaining.
+                */
+               if (!ssh->fallback_cmd &&
+                   *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
+                   logevent("Primary command failed; attempting fallback");
+                   ssh->fallback_cmd = TRUE;
+                   continue;
+               }
+               bombout(("Server refused to start a shell/command"));
                crStopV;
+           } else {
+               logevent("Started a shell/command");
            }
-           /*
-            * We failed to start the command. If this is the
-            * fallback command, we really are finished; if it's
-            * not, and if the fallback command exists, try falling
-            * back to it before complaining.
-            */
-           if (!ssh->fallback_cmd &&
-               *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
-               logevent("Primary command failed; attempting fallback");
-               ssh->fallback_cmd = TRUE;
-               continue;
-           }
-           bombout(("Server refused to start a shell/command"));
-           crStopV;
-       } else {
-           logevent("Started a shell/command");
+           break;
        }
-       break;
+    } else {
+       ssh->editing = ssh->echoing = TRUE;
     }
 
     ssh->state = SSH_STATE_SESSION;
@@ -9226,18 +10497,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     if (ssh->eof_needed)
        ssh_special(ssh, TS_EOF);
 
-    /*
-     * All the initial channel requests are done, so install the default
-     * failure handler.
-     */
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
-
     /*
      * Transfer data!
      */
     if (ssh->ldisc)
-       ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+       ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
     if (ssh->mainchan)
        ssh->send_ok = 1;
     while (1) {
@@ -9319,6 +10583,25 @@ static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
     logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
 }
 
+static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
+{
+    do_ssh2_transport(ssh, NULL, 0, pktin);
+}
+
+/*
+ * Called if we receive a packet that isn't allowed by the protocol.
+ * This only applies to packets whose meaning PuTTY understands.
+ * Entirely unknown packets are handled below.
+ */
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin)
+{
+    char *buf = dupprintf("Server protocol violation: unexpected %s packet",
+                         ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+                                       pktin->type));
+    ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+    sfree(buf);
+}
+
 static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin)
 {
     struct Packet *pktout;
@@ -9345,60 +10628,105 @@ static void ssh2_protocol_setup(Ssh ssh)
        ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
 
     /*
-     * Any message we actually understand, we set to NULL so that
-     * the coroutines will get it.
+     * Initially, we only accept transport messages (and a few generic
+     * ones).  do_ssh2_authconn will add more when it starts.
+     * Messages that are understood but not currently acceptable go to
+     * ssh2_msg_unexpected.
      */
-    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL;
-    /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */
-    /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */
-    ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL;
-    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */
-    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL;
+    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport;
+    ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport;
+    ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport;
+    ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport;
+    /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */
+    /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */
+    ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport;
+    ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected;
+    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */
+    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
 
     /*
-     * These special message types we install handlers for.
+     * These messages have a special handler from the start.
      */
     ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
     ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */
     ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
 }
 
-static void ssh2_timer(void *ctx, long now)
+static void ssh2_bare_connection_protocol_setup(Ssh ssh)
+{
+    int i;
+
+    /*
+     * Most messages cause SSH2_MSG_UNIMPLEMENTED.
+     */
+    for (i = 0; i < 256; i++)
+       ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
+
+    /*
+     * Initially, we set all ssh-connection messages to 'unexpected';
+     * do_ssh2_authconn will fill things in properly. We also handle a
+     * couple of messages from the transport protocol which aren't
+     * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG,
+     * DISCONNECT).
+     */
+    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
+
+    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+
+    /*
+     * These messages have a special handler from the start.
+     */
+    ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
+    ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore;
+    ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
+}
+
+static void ssh2_timer(void *ctx, unsigned long now)
 {
     Ssh ssh = (Ssh)ctx;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (!ssh->kex_in_progress && conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
-       now - ssh->next_rekey >= 0) {
+    if (!ssh->kex_in_progress && !ssh->bare_connection &&
+        conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
+       now == ssh->next_rekey) {
        do_ssh2_transport(ssh, "timeout", -1, NULL);
     }
 }
@@ -9418,24 +10746,25 @@ static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
            do_ssh2_transport(ssh, "too much data received", -1, NULL);
     }
 
-    if (pktin && ssh->packet_dispatch[pktin->type]) {
+    if (pktin)
        ssh->packet_dispatch[pktin->type](ssh, pktin);
+    else if (!ssh->protocol_initial_phase_done)
+       do_ssh2_transport(ssh, in, inlen, pktin);
+    else
+       do_ssh2_authconn(ssh, in, inlen, pktin);
+}
+
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+                                          struct Packet *pktin)
+{
+    unsigned char *in = (unsigned char *)vin;
+    if (ssh->state == SSH_STATE_CLOSED)
        return;
-    }
 
-    if (!ssh->protocol_initial_phase_done ||
-       (pktin && pktin->type >= 20 && pktin->type < 50)) {
-       if (do_ssh2_transport(ssh, in, inlen, pktin) &&
-           !ssh->protocol_initial_phase_done) {
-           ssh->protocol_initial_phase_done = TRUE;
-           /*
-            * Allow authconn to initialise itself.
-            */
-           do_ssh2_authconn(ssh, NULL, 0, NULL);
-       }
-    } else {
-       do_ssh2_authconn(ssh, in, inlen, pktin);
-    }
+    if (pktin)
+       ssh->packet_dispatch[pktin->type](ssh, pktin);
+    else
+        do_ssh2_authconn(ssh, in, inlen, pktin);
 }
 
 static void ssh_cache_conf_values(Ssh ssh)
@@ -9478,6 +10807,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->kex = NULL;
     ssh->kex_ctx = NULL;
     ssh->hostkey = NULL;
+    ssh->hostkey_str = NULL;
     ssh->exitcode = -1;
     ssh->close_expected = FALSE;
     ssh->clean_exit = FALSE;
@@ -9493,17 +10823,17 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
     ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
     ssh->x11disp = NULL;
+    ssh->x11auth = NULL;
+    ssh->x11authtree = newtree234(x11_authcmp);
     ssh->v1_compressing = FALSE;
     ssh->v2_outgoing_sequence = 0;
     ssh->ssh1_rdpkt_crstate = 0;
     ssh->ssh2_rdpkt_crstate = 0;
-    ssh->do_ssh_init_crstate = 0;
+    ssh->ssh2_bare_rdpkt_crstate = 0;
     ssh->ssh_gotdata_crstate = 0;
     ssh->do_ssh1_connection_crstate = 0;
-    ssh->do_ssh1_login_crstate = 0;
-    ssh->do_ssh2_transport_crstate = 0;
-    ssh->do_ssh2_authconn_crstate = 0;
     ssh->do_ssh_init_state = NULL;
+    ssh->do_ssh_connection_init_state = NULL;
     ssh->do_ssh1_login_state = NULL;
     ssh->do_ssh2_transport_state = NULL;
     ssh->do_ssh2_authconn_state = NULL;
@@ -9522,6 +10852,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->username = NULL;
     ssh->sent_console_eof = FALSE;
     ssh->got_pty = FALSE;
+    ssh->bare_connection = FALSE;
+    ssh->X11_fwd_enabled = FALSE;
+    ssh->connshare = NULL;
+    ssh->attempting_connshare = FALSE;
 
     *backend_handle = ssh;
 
@@ -9561,11 +10895,13 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->gsslibs = NULL;
 #endif
 
+    random_ref(); /* do this now - may be needed by sharing setup code */
+
     p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
-    if (p != NULL)
+    if (p != NULL) {
+        random_unref();
        return p;
-
-    random_ref();
+    }
 
     return NULL;
 }
@@ -9575,6 +10911,7 @@ static void ssh_free(void *handle)
     Ssh ssh = (Ssh) handle;
     struct ssh_channel *c;
     struct ssh_rportfwd *pf;
+    struct X11FakeAuth *auth;
 
     if (ssh->v1_cipher_ctx)
        ssh->cipher->free_context(ssh->v1_cipher_ctx);
@@ -9609,7 +10946,7 @@ static void ssh_free(void *handle)
     while (ssh->qhead) {
        struct queued_handler *qh = ssh->qhead;
        ssh->qhead = qh->next;
-       sfree(ssh->qhead);
+       sfree(qh);
     }
     ssh->qhead = ssh->qtail = NULL;
 
@@ -9617,21 +10954,35 @@ static void ssh_free(void *handle)
        while ((c = delpos234(ssh->channels, 0)) != NULL) {
            switch (c->type) {
              case CHAN_X11:
-               if (c->u.x11.s != NULL)
-                   x11_close(c->u.x11.s);
+               if (c->u.x11.xconn != NULL)
+                   x11_close(c->u.x11.xconn);
                break;
              case CHAN_SOCKDATA:
              case CHAN_SOCKDATA_DORMANT:
-               if (c->u.pfd.s != NULL)
-                   pfd_close(c->u.pfd.s);
+               if (c->u.pfd.pf != NULL)
+                   pfd_close(c->u.pfd.pf);
                break;
            }
+           if (ssh->version == 2) {
+               struct outstanding_channel_request *ocr, *nocr;
+               ocr = c->v.v2.chanreq_head;
+               while (ocr) {
+                   ocr->handler(c, NULL, ocr->ctx);
+                   nocr = ocr->next;
+                   sfree(ocr);
+                   ocr = nocr;
+               }
+               bufchain_clear(&c->v.v2.outbuffer);
+           }
            sfree(c);
        }
        freetree234(ssh->channels);
        ssh->channels = NULL;
     }
 
+    if (ssh->connshare)
+        sharestate_free(ssh->connshare);
+
     if (ssh->rportfwds) {
        while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
            free_rportfwd(pf);
@@ -9641,6 +10992,9 @@ static void ssh_free(void *handle)
     sfree(ssh->deferred_send_data);
     if (ssh->x11disp)
        x11_free_display(ssh->x11disp);
+    while ((auth = delpos234(ssh->x11authtree, 0)) != NULL)
+        x11_free_fake_auth(auth);
+    freetree234(ssh->x11authtree);
     sfree(ssh->do_ssh_init_state);
     sfree(ssh->do_ssh1_login_state);
     sfree(ssh->do_ssh2_transport_state);
@@ -9648,6 +11002,7 @@ static void ssh_free(void *handle)
     sfree(ssh->v_c);
     sfree(ssh->v_s);
     sfree(ssh->fullhostname);
+    sfree(ssh->hostkey_str);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
@@ -9686,10 +11041,10 @@ static void ssh_reconfig(void *handle, Conf *conf)
     rekey_time = conf_get_int(conf, CONF_ssh_rekey_time);
     if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time &&
        rekey_time != 0) {
-       long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
-       long now = GETTICKCOUNT();
+       unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
+       unsigned long now = GETTICKCOUNT();
 
-       if (new_next - now < 0) {
+       if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) {
            rekeying = "timeout shortened";
        } else {
            ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
@@ -9728,7 +11083,7 @@ static void ssh_reconfig(void *handle, Conf *conf)
     ssh->conf = conf_copy(conf);
     ssh_cache_conf_values(ssh);
 
-    if (rekeying) {
+    if (!ssh->bare_connection && rekeying) {
        if (!ssh->kex_in_progress) {
            do_ssh2_transport(ssh, rekeying, -1, NULL);
        } else if (rekey_mandatory) {
@@ -9811,10 +11166,8 @@ static void ssh_size(void *handle, int width, int height)
                            PKT_INT, ssh->term_width,
                            PKT_INT, 0, PKT_INT, 0, PKT_END);
            } else if (ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "window-change");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "window-change",
+                                          NULL, NULL);
                ssh2_pkt_adduint32(pktout, ssh->term_width);
                ssh2_pkt_adduint32(pktout, ssh->term_height);
                ssh2_pkt_adduint32(pktout, 0);
@@ -9885,7 +11238,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     } else if (ssh->version == 2) {
        if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE))
            ADD_SPECIALS(ssh2_ignore_special);
-       if (!(ssh->remote_bugs & BUG_SSH2_REKEY))
+       if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection)
            ADD_SPECIALS(ssh2_rekey_special);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
@@ -9941,7 +11294,8 @@ static void ssh_special(void *handle, Telnet_Special code)
            }
        }
     } else if (code == TS_REKEY) {
-       if (!ssh->kex_in_progress && ssh->version == 2) {
+       if (!ssh->kex_in_progress && !ssh->bare_connection &&
+            ssh->version == 2) {
            do_ssh2_transport(ssh, "at user request", -1, NULL);
        }
     } else if (code == TS_BRK) {
@@ -9950,10 +11304,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (ssh->version == 1) {
            logevent("Unable to send BREAK signal in SSH-1");
        } else if (ssh->mainchan) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(pktout, "break");
-           ssh2_pkt_addbool(pktout, 0);
+           pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL);
            ssh2_pkt_adduint32(pktout, 0);   /* default break length */
            ssh2_pkt_send(ssh, pktout);
        }
@@ -9978,10 +11329,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (signame) {
            /* It's a signal. */
            if (ssh->version == 2 && ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "signal");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL);
                ssh2_pkt_addstring(pktout, signame);
                ssh2_pkt_send(ssh, pktout);
                logeventf(ssh, "Sent signal SIG%s", signame);
@@ -9992,7 +11340,7 @@ static void ssh_special(void *handle, Telnet_Special code)
     }
 }
 
-void *new_sock_channel(void *handle, Socket s)
+void *new_sock_channel(void *handle, struct PortForwarding *pf)
 {
     Ssh ssh = (Ssh) handle;
     struct ssh_channel *c;
@@ -10002,11 +11350,46 @@ void *new_sock_channel(void *handle, Socket s)
     ssh2_channel_init(c);
     c->halfopen = TRUE;
     c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
-    c->u.pfd.s = s;
+    c->u.pfd.pf = pf;
     add234(ssh->channels, c);
     return c;
 }
 
+unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx)
+{
+    struct ssh_channel *c;
+    c = snew(struct ssh_channel);
+
+    c->ssh = ssh;
+    ssh2_channel_init(c);
+    c->type = CHAN_SHARING;
+    c->u.sharing.ctx = sharing_ctx;
+    add234(ssh->channels, c);
+    return c->localid;
+}
+
+void ssh_delete_sharing_channel(Ssh ssh, unsigned localid)
+{
+    struct ssh_channel *c;
+
+    c = find234(ssh->channels, &localid, ssh_channelfind);
+    if (c)
+        ssh_channel_destroy(c);
+}
+
+void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
+                                     const void *data, int datalen,
+                                     const char *additional_log_text)
+{
+    struct Packet *pkt;
+
+    pkt = ssh2_pkt_init(type);
+    pkt->downstream_id = id;
+    pkt->additional_log_text = additional_log_text;
+    ssh2_pkt_adddata(pkt, data, datalen);
+    ssh2_pkt_send(ssh, pkt);
+}
+
 /*
  * This is called when stdout/stderr (the entity to which
  * from_backend sends data) manages to clear some backlog.
@@ -10026,7 +11409,7 @@ static void ssh_unthrottle(void *handle, int bufsize)
            ssh2_set_window(ssh->mainchan,
                            bufsize < ssh->mainchan->v.v2.locmaxwin ?
                            ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
-           if (conf_get_int(ssh->conf, CONF_ssh_simple))
+           if (ssh_is_simple(ssh))
                buflimit = 0;
            else
                buflimit = ssh->mainchan->v.v2.locmaxwin;
@@ -10036,6 +11419,12 @@ static void ssh_unthrottle(void *handle, int bufsize)
            }
        }
     }
+
+    /*
+     * Now process any SSH connection data that was stashed in our
+     * queue while we were frozen.
+     */
+    ssh_process_queued_incoming_data(ssh);
 }
 
 void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
@@ -10044,7 +11433,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
     Ssh ssh = c->ssh;
     struct Packet *pktout;
 
-    logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port);
+    logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org);
 
     if (ssh->version == 1) {
        send_packet(ssh, SSH1_MSG_PORT_OPEN,
@@ -10054,12 +11443,12 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
                    /* PKT_STR, <org:orgport>, */
                    PKT_END);
     } else {
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
-       ssh2_pkt_addstring(pktout, "direct-tcpip");
-       ssh2_pkt_adduint32(pktout, c->localid);
-       ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
-       ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT);      /* our max pkt size */
-       ssh2_pkt_addstring(pktout, hostname);
+       pktout = ssh2_chanopen_init(c, "direct-tcpip");
+        {
+            char *trimmed_host = host_strduptrim(hostname);
+            ssh2_pkt_addstring(pktout, trimmed_host);
+            sfree(trimmed_host);
+        }
        ssh2_pkt_adduint32(pktout, port);
        /*
         * We make up values for the originator data; partly it's
@@ -10120,13 +11509,19 @@ static int ssh_return_exitcode(void *handle)
 }
 
 /*
- * cfg_info for SSH is the currently running version of the
- * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ * cfg_info for SSH is the protocol running in this session.
+ * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare
+ * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.)
  */
 static int ssh_cfg_info(void *handle)
 {
     Ssh ssh = (Ssh) handle;
-    return ssh->version;
+    if (ssh->version == 0)
+       return 0; /* don't know yet */
+    else if (ssh->bare_connection)
+       return -1;
+    else
+       return ssh->version;
 }
 
 /*