]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - ssh.c
Fix resource leak in rsakey_pubblob
[PuTTY.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index 04fc14b54a22fff50abfb26dffe4fb001b31085b..7e74fb44915ef161f5abf8ede3bae514cdb0dd10 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -89,7 +89,7 @@ static const char *const ssh2_disconnect_reasons[] = {
  * This list is derived from RFC 4254 and
  * SSH-1 RFC-1.2.31.
  */
-static const struct {
+static const struct ssh_ttymode {
     const char* const mode;
     int opcode;
     enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
@@ -125,6 +125,7 @@ static const struct {
     { "IXANY",      39, TTY_OP_BOOL },
     { "IXOFF",      40, TTY_OP_BOOL },
     { "IMAXBEL",     41, TTY_OP_BOOL },
+    { "IUTF8",       42, TTY_OP_BOOL },
     { "ISIG",       50, TTY_OP_BOOL },
     { "ICANON",             51, TTY_OP_BOOL },
     { "XCASE",      52, TTY_OP_BOOL },
@@ -363,8 +364,13 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
                         struct Packet *pktin);
 static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                             struct Packet *pktin);
+static void ssh_channel_init(struct ssh_channel *c);
+static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin);
+static void ssh_channel_got_eof(struct ssh_channel *c);
 static void ssh2_channel_check_close(struct ssh_channel *c);
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason);
 static void ssh_channel_destroy(struct ssh_channel *c);
+static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize);
 static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
 
 /*
@@ -384,7 +390,7 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
  *    ensure that the server never has any need to throttle its end
  *    of the connection), so we set this high as well.
  * 
- *  - OUR_V2_WINSIZE is the maximum window size we present on SSH-2
+ *  - OUR_V2_WINSIZE is the default window size we present on SSH-2
  *    channels.
  *
  *  - OUR_V2_BIGWIN is the window size we advertise for the only
@@ -408,16 +414,23 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
 #define OUR_V2_MAXPKT 0x4000UL
 #define OUR_V2_PACKETLIMIT 0x9000UL
 
-const static struct ssh_signkey *hostkey_algs[] = {
-    &ssh_ecdsa_ed25519,
-    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
-    &ssh_rsa, &ssh_dss
+struct ssh_signkey_with_user_pref_id {
+    const struct ssh_signkey *alg;
+    int id;
+};
+const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
+    { &ssh_ecdsa_ed25519, HK_ED25519 },
+    { &ssh_ecdsa_nistp256, HK_ECDSA },
+    { &ssh_ecdsa_nistp384, HK_ECDSA },
+    { &ssh_ecdsa_nistp521, HK_ECDSA },
+    { &ssh_dss, HK_DSA },
+    { &ssh_rsa, HK_RSA },
 };
 
-const static struct ssh_mac *macs[] = {
+const static struct ssh_mac *const macs[] = {
     &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
-const static struct ssh_mac *buggymacs[] = {
+const static struct ssh_mac *const buggymacs[] = {
     &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
 };
 
@@ -444,7 +457,7 @@ const static struct ssh_compress ssh_comp_none = {
     ssh_comp_none_disable, NULL
 };
 extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
+const static struct ssh_compress *const compressions[] = {
     &ssh_zlib, &ssh_comp_none
 };
 
@@ -453,7 +466,6 @@ enum {                                     /* channel types */
     CHAN_X11,
     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
@@ -701,8 +713,8 @@ 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);
-static void ssh2_add_channel_data(struct ssh_channel *c,
-                                  const char *buf, int len);
+static int ssh_send_channel_data(struct ssh_channel *c,
+                                const char *buf, int len);
 static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
 static void ssh2_set_window(struct ssh_channel *c, int newwin);
 static int ssh_sendbuffer(void *handle);
@@ -975,6 +987,14 @@ struct ssh_tag {
     int cross_certifying;
 };
 
+static const char *ssh_pkt_type(Ssh ssh, int type)
+{
+    if (ssh->version == 1)
+       return ssh1_pkt_type(type);
+    else
+       return ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, type);
+}
+
 #define logevent(s) logevent(ssh->frontend, s)
 
 /* logevent, only printf-formatted. */
@@ -1002,27 +1022,37 @@ static void bomb_out(Ssh ssh, char *text)
 
 /* Helper function for common bits of parsing ttymodes. */
 static void parse_ttymodes(Ssh ssh,
-                          void (*do_mode)(void *data, char *mode, char *val),
+                           void (*do_mode)(void *data,
+                                           const struct ssh_ttymode *mode,
+                                           char *val),
                           void *data)
 {
-    char *key, *val;
+    int i;
+    const struct ssh_ttymode *mode;
+    char *val;
+    char default_val[2];
+
+    strcpy(default_val, "A");
+
+    for (i = 0; i < lenof(ssh_ttymodes); i++) {
+        mode = ssh_ttymodes + i;
+        val = conf_get_str_str_opt(ssh->conf, CONF_ttymodes, mode->mode);
+        if (!val)
+            val = default_val;
 
-    for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key);
-        val != NULL;
-        val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) {
        /*
         * val[0] is either 'V', indicating that an explicit value
         * follows it, or 'A' indicating that we should pass the
         * value through from the local environment via get_ttymode.
         */
        if (val[0] == 'A') {
-           val = get_ttymode(ssh->frontend, key);
+           val = get_ttymode(ssh->frontend, mode->mode);
            if (val) {
-               do_mode(data, key, val);
+               do_mode(data, mode, val);
                sfree(val);
            }
        } else
-           do_mode(data, key, val + 1);               /* skip the 'V' */
+            do_mode(data, mode, val + 1);              /* skip the 'V' */
     }
 }
 
@@ -2514,13 +2544,15 @@ static void ssh_pkt_defersend(Ssh ssh)
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
 
-    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);
-    ssh->deferred_data_size = 0;
+    if (ssh->version == 2) {
+       ssh->outgoing_data_size += ssh->deferred_data_size;
+       ssh->deferred_data_size = 0;
+       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);
+    }
 }
 
 /*
@@ -3131,15 +3163,21 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     /* Anything greater or equal to "1.99" means protocol 2 is supported. */
     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 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 configuration but "
-                "not provided by server"));
-       crStop(0);
+    if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+       if (!s->proto1) {
+           bombout(("SSH protocol version 1 required by our configuration "
+                    "but not provided by server"));
+           crStop(0);
+       }
+    } else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) {
+       if (!s->proto2) {
+           bombout(("SSH protocol version 2 required by our configuration "
+                    "but server only provides (old, insecure) SSH-1"));
+           crStop(0);
+       }
+    } else {
+       /* No longer support values 1 or 2 for CONF_sshprot */
+       assert(!"Unexpected value for CONF_sshprot");
     }
 
     if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
@@ -3427,15 +3465,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
      */
     if (ssh->channels) {
        while (NULL != (c = index234(ssh->channels, 0))) {
-           switch (c->type) {
-             case CHAN_X11:
-               x11_close(c->u.x11.xconn);
-               break;
-             case CHAN_SOCKDATA:
-             case CHAN_SOCKDATA_DORMANT:
-               pfd_close(c->u.pfd.pf);
-               break;
-           }
+           ssh_channel_close_local(c, NULL);
            del234(ssh->channels, c); /* moving next one to index 0 */
            if (ssh->version == 2)
                bufchain_clear(&c->v.v2.outbuffer);
@@ -3701,13 +3731,17 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port,
     }
 
     /*
-     * If the SSH version number's fixed, set it now, and if it's SSH-2,
-     * send the version string too.
+     * The SSH version number is always fixed (since we no longer support
+     * fallback between versions), so set it now, and if it's SSH-2,
+     * send the version string now too.
      */
     sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+    assert(sshprot == 0 || sshprot == 3);
     if (sshprot == 0)
+       /* SSH-1 only */
        ssh->version = 1;
     if (sshprot == 3 && !ssh->bare_connection) {
+       /* SSH-2 only */
        ssh->version = 2;
        ssh_send_verstring(ssh, "SSH-", NULL);
     }
@@ -3807,7 +3841,6 @@ static void ssh_dialog_callback(void *sshv, int ret)
 static void ssh_agentf_callback(void *cv, void *reply, int replylen)
 {
     struct ssh_channel *c = (struct ssh_channel *)cv;
-    Ssh ssh = c->ssh;
     const void *sentreply = reply;
 
     c->u.a.outstanding_requests--;
@@ -3816,16 +3849,7 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
        sentreply = "\0\0\0\1\5";
        replylen = 5;
     }
-    if (ssh->version == 2) {
-       ssh2_add_channel_data(c, sentreply, replylen);
-       ssh2_try_send(c);
-    } else {
-       send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
-                   PKT_INT, c->remoteid,
-                   PKT_INT, replylen,
-                   PKT_DATA, sentreply, replylen,
-                   PKT_END);
-    }
+    ssh_send_channel_data(c, sentreply, replylen);
     if (reply)
        sfree(reply);
     /*
@@ -4972,23 +4996,14 @@ void sshfwd_write_eof(struct ssh_channel *c)
 void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
 {
     Ssh ssh = c->ssh;
+    char *reason;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    switch (c->type) {
-      case CHAN_X11:
-        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.pf);
-        logeventf(ssh, "Forwarded port closed due to local error: %s", err);
-        break;
-    }
-    c->type = CHAN_ZOMBIE;
+    reason = dupprintf("due to local error: %s", err);
+    ssh_channel_close_local(c, reason);
+    sfree(reason);
     c->pending_eof = FALSE;   /* this will confuse a zombie channel */
 
     ssh2_channel_check_close(c);
@@ -5001,43 +5016,17 @@ int sshfwd_write(struct ssh_channel *c, char *buf, int len)
     if (ssh->state == SSH_STATE_CLOSED)
        return 0;
 
-    if (ssh->version == 1) {
-       send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
-                   PKT_INT, c->remoteid,
-                   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
-        * the only circumstance that can cause throttling will be
-        * the whole SSH connection backing up, in which case
-        * _everything_ will be throttled as a whole.
-        */
-       return 0;
-    } else {
-       ssh2_add_channel_data(c, buf, len);
-       return ssh2_try_send(c);
-    }
+    return ssh_send_channel_data(c, buf, len);
 }
 
 void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
 {
     Ssh ssh = c->ssh;
-    int buflimit;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (ssh->version == 1) {
-       buflimit = SSH1_BUFFER_LIMIT;
-    } else {
-       buflimit = c->v.v2.locmaxwin;
-       ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0);
-    }
-    if (c->throttling_conn && bufsize <= buflimit) {
-       c->throttling_conn = 0;
-       ssh_throttle_conn(ssh, -1);
-    }
+    ssh_channel_unthrottle(c, bufsize);
 }
 
 static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
@@ -5520,15 +5509,11 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
        c = snew(struct ssh_channel);
        c->ssh = ssh;
 
+       ssh_channel_init(c);
        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);
@@ -5550,17 +5535,13 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
     } else {
        c = snew(struct ssh_channel);
        c->ssh = ssh;
+       ssh_channel_init(c);
        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_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,
                    PKT_END);
@@ -5605,14 +5586,10 @@ 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 {
+           ssh_channel_init(c);
            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_SOCKDATA;    /* 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);
@@ -5625,15 +5602,12 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
 
 static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
 {
-    unsigned int remoteid = ssh_pkt_getuint32(pktin);
-    unsigned int localid = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
 
-    c = find234(ssh->channels, &remoteid, ssh_channelfind);
-    if (c && c->type == CHAN_SOCKDATA_DORMANT) {
-       c->remoteid = localid;
+    c = ssh_channel_msg(ssh, pktin);
+    if (c && c->type == CHAN_SOCKDATA) {
+       c->remoteid = ssh_pkt_getuint32(pktin);
        c->halfopen = FALSE;
-       c->type = CHAN_SOCKDATA;
        c->throttling_conn = 0;
        pfd_confirm(c->u.pfd.pf);
     }
@@ -5651,11 +5625,10 @@ static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
 
 static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
 {
-    unsigned int remoteid = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
 
-    c = find234(ssh->channels, &remoteid, ssh_channelfind);
-    if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+    c = ssh_channel_msg(ssh, pktin);
+    if (c && c->type == CHAN_SOCKDATA) {
        logevent("Forwarded connection refused by server");
        pfd_close(c->u.pfd.pf);
        del234(ssh->channels, c);
@@ -5666,52 +5639,26 @@ static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
 static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
 {
     /* Remote side closes a channel. */
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
-    c = find234(ssh->channels, &i, ssh_channelfind);
-    if (c && !c->halfopen) {
 
-        if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
-            !(c->closes & CLOSES_RCVD_EOF)) {
+    c = ssh_channel_msg(ssh, pktin);
+    if (c) {
+
+        if (pktin->type == SSH1_MSG_CHANNEL_CLOSE) {
             /*
              * Received CHANNEL_CLOSE, which we translate into
              * outgoing EOF.
              */
-            int send_close = FALSE;
-
-            c->closes |= CLOSES_RCVD_EOF;
-
-            switch (c->type) {
-              case CHAN_X11:
-                if (c->u.x11.xconn)
-                    x11_send_eof(c->u.x11.xconn);
-                else
-                    send_close = TRUE;
-               break;
-              case CHAN_SOCKDATA:
-                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)) {
-                send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
-                            PKT_END);
-                c->closes |= CLOSES_SENT_EOF;
-            }
+           ssh_channel_got_eof(c);
         }
 
         if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION &&
             !(c->closes & CLOSES_RCVD_CLOSE)) {
 
             if (!(c->closes & CLOSES_SENT_EOF)) {
-                bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d"
-                         " for which we never sent CHANNEL_CLOSE\n", i));
+                bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %u"
+                         " for which we never sent CHANNEL_CLOSE\n",
+                        c->localid));
             }
 
             c->closes |= CLOSES_RCVD_CLOSE;
@@ -5726,78 +5673,79 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
 
        if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes))
             ssh_channel_destroy(c);
-    } else {
-       bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
-                pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" :
-                "_CONFIRMATION", c ? "half-open" : "nonexistent",
-                i));
     }
 }
 
+/*
+ * Handle incoming data on an SSH-1 or SSH-2 agent-forwarding channel.
+ */
+static int ssh_agent_channel_data(struct ssh_channel *c, char *data,
+                                 int length)
+{
+    while (length > 0) {
+       if (c->u.a.lensofar < 4) {
+           unsigned int l = min(4 - c->u.a.lensofar, (unsigned)length);
+           memcpy(c->u.a.msglen + c->u.a.lensofar, data, l);
+           data += l;
+           length -= l;
+           c->u.a.lensofar += l;
+       }
+       if (c->u.a.lensofar == 4) {
+           c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
+           c->u.a.message = snewn(c->u.a.totallen, unsigned char);
+           memcpy(c->u.a.message, c->u.a.msglen, 4);
+       }
+       if (c->u.a.lensofar >= 4 && length > 0) {
+           unsigned int l = min(c->u.a.totallen - c->u.a.lensofar,
+                                (unsigned)length);
+           memcpy(c->u.a.message + c->u.a.lensofar, data, l);
+           data += l;
+           length -= l;
+           c->u.a.lensofar += l;
+       }
+       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,
+                           ssh_agentf_callback, c))
+               ssh_agentf_callback(c, reply, replylen);
+           sfree(c->u.a.message);
+            c->u.a.message = NULL;
+           c->u.a.lensofar = 0;
+       }
+    }
+    return 0;   /* agent channels never back up */
+}
+
+static int ssh_channel_data(struct ssh_channel *c, int is_stderr,
+                           char *data,  int length)
+{
+    switch (c->type) {
+      case CHAN_MAINSESSION:
+       return from_backend(c->ssh->frontend, is_stderr, data, length);
+      case CHAN_X11:
+       return x11_send(c->u.x11.xconn, data, length);
+      case CHAN_SOCKDATA:
+       return pfd_send(c->u.pfd.pf, data, length);
+      case CHAN_AGENT:
+       return ssh_agent_channel_data(c, data, length);
+    }
+    return 0;
+}
+
 static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
 {
     /* Data sent down one of our channels. */
-    int i = ssh_pkt_getuint32(pktin);
     char *p;
     int len;
     struct ssh_channel *c;
 
+    c = ssh_channel_msg(ssh, pktin);
     ssh_pkt_getstring(pktin, &p, &len);
 
-    c = find234(ssh->channels, &i, ssh_channelfind);
     if (c) {
-       int bufsize = 0;
-       switch (c->type) {
-         case CHAN_X11:
-           bufsize = x11_send(c->u.x11.xconn, p, len);
-           break;
-         case CHAN_SOCKDATA:
-           bufsize = pfd_send(c->u.pfd.pf, p, len);
-           break;
-         case CHAN_AGENT:
-           /* Data for an agent message. Buffer it. */
-           while (len > 0) {
-               if (c->u.a.lensofar < 4) {
-                   unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
-                   memcpy(c->u.a.msglen + c->u.a.lensofar, p,
-                          l);
-                   p += l;
-                   len -= l;
-                   c->u.a.lensofar += l;
-               }
-               if (c->u.a.lensofar == 4) {
-                   c->u.a.totallen =
-                       4 + GET_32BIT(c->u.a.msglen);
-                   c->u.a.message = snewn(c->u.a.totallen,
-                                          unsigned char);
-                   memcpy(c->u.a.message, c->u.a.msglen, 4);
-               }
-               if (c->u.a.lensofar >= 4 && len > 0) {
-                   unsigned int l =
-                       min(c->u.a.totallen - c->u.a.lensofar,
-                           (unsigned)len);
-                   memcpy(c->u.a.message + c->u.a.lensofar, p,
-                          l);
-                   p += l;
-                   len -= l;
-                   c->u.a.lensofar += l;
-               }
-               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,
-                                   ssh_agentf_callback, c))
-                       ssh_agentf_callback(c, reply, replylen);
-                   sfree(c->u.a.message);
-                   c->u.a.lensofar = 0;
-               }
-           }
-           bufsize = 0;   /* agent channels never back up */
-           break;
-       }
+       int bufsize = ssh_channel_data(c, FALSE, p, len);
        if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
            c->throttling_conn = 1;
            ssh_throttle_conn(ssh, +1);
@@ -5821,14 +5769,13 @@ static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
 }
 
 /* Helper function to deal with sending tty modes for REQUEST_PTY */
-static void ssh1_send_ttymode(void *data, char *mode, char *val)
+static void ssh1_send_ttymode(void *data,
+                              const struct ssh_ttymode *mode, char *val)
 {
     struct Packet *pktout = (struct Packet *)data;
-    int i = 0;
     unsigned int arg = 0;
-    while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
-    if (i == lenof(ssh_ttymodes)) return;
-    switch (ssh_ttymodes[i].type) {
+
+    switch (mode->type) {
       case TTY_OP_CHAR:
        arg = ssh_tty_parse_specchar(val);
        break;
@@ -5836,7 +5783,7 @@ static void ssh1_send_ttymode(void *data, char *mode, char *val)
        arg = ssh_tty_parse_boolean(val);
        break;
     }
-    ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+    ssh2_pkt_addbyte(pktout, mode->opcode);
     ssh2_pkt_addbyte(pktout, arg);
 }
 
@@ -6242,7 +6189,10 @@ struct kexinit_algorithm {
            const struct ssh_kex *kex;
            int warn;
        } kex;
-       const struct ssh_signkey *hostkey;
+       struct {
+            const struct ssh_signkey *hostkey;
+            int warn;
+        } hk;
        struct {
            const struct ssh2_cipher *cipher;
            int warn;
@@ -6297,12 +6247,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        "server-to-client compression method" };
     struct do_ssh2_transport_state {
        int crLine;
-       int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
+       int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
        Bignum p, g, e, f, K;
        void *our_kexinit;
        int our_kexinitlen;
        int kex_init_value, kex_reply_value;
-       const struct ssh_mac **maclist;
+       const struct ssh_mac *const *maclist;
        int nmacs;
        const struct ssh2_cipher *cscipher_tobe;
        const struct ssh2_cipher *sccipher_tobe;
@@ -6319,6 +6269,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
        const struct ssh_kexes *preferred_kex[KEX_MAX];
+       int n_preferred_hk;
+       int preferred_hk[HK_MAX];
        int n_preferred_ciphers;
        const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
        const struct ssh_compress *preferred_comp;
@@ -6334,6 +6286,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
     crState(do_ssh2_transport_state);
 
     assert(!ssh->bare_connection);
+    assert(ssh->version == 2);
 
     crBeginState;
 
@@ -6395,6 +6348,20 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
            }
        }
 
+       /*
+        * Set up the preferred host key types. These are just the ids
+        * in the enum in putty.h, so 'warn below here' is indicated
+        * by HK_WARN.
+        */
+       s->n_preferred_hk = 0;
+       for (i = 0; i < HK_MAX; i++) {
+            int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
+            /* As above, don't bother with HK_WARN if it's last in the
+             * list */
+           if (id != HK_WARN || i < HK_MAX - 1)
+                s->preferred_hk[s->n_preferred_hk++] = id;
+       }
+
        /*
         * Set up the preferred ciphers. (NULL => warn below here)
         */
@@ -6471,20 +6438,43 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
              * In the first key exchange, we list all the algorithms
              * we're prepared to cope with, but prefer those algorithms
             * for which we have a host key for this host.
+             *
+             * If the host key algorithm is below the warning
+             * threshold, we warn even if we did already have a key
+             * for it, on the basis that if the user has just
+             * reconfigured that host key type to be warned about,
+             * they surely _do_ want to be alerted that a server
+             * they're actually connecting to is using it.
              */
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-               if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
-                                     hostkey_algs[i]->keytype)) {
-                   alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-                                             hostkey_algs[i]->name);
-                   alg->u.hostkey = hostkey_algs[i];
-               }
-           }
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-               alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-                                         hostkey_algs[i]->name);
-               alg->u.hostkey = hostkey_algs[i];
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                          hostkey_algs[j].alg->keytype)) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                                  hostkey_algs[j].alg->name);
+                        alg->u.hk.hostkey = hostkey_algs[j].alg;
+                        alg->u.hk.warn = warn;
+                    }
+                }
            }
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                              hostkey_algs[j].alg->name);
+                    alg->u.hk.hostkey = hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
         } else {
             /*
              * In subsequent key exchanges, we list only the kex
@@ -6496,7 +6486,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
             assert(ssh->kex);
            alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
                                      ssh->hostkey->name);
-           alg->u.hostkey = ssh->hostkey;
+           alg->u.hk.hostkey = ssh->hostkey;
+            alg->u.hk.warn = FALSE;
         }
        /* List encryption algorithms (client->server then server->client). */
        for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
@@ -6617,7 +6608,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        s->scmac_tobe = NULL;
        s->cscomp_tobe = NULL;
        s->sccomp_tobe = NULL;
-       s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+       s->warn_kex = s->warn_hk = FALSE;
+        s->warn_cscipher = s->warn_sccipher = FALSE;
 
        pktin->savedpos += 16;          /* skip garbage cookie */
 
@@ -6661,7 +6653,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                        ssh->kex = alg->u.kex.kex;
                        s->warn_kex = alg->u.kex.warn;
                    } else if (i == KEXLIST_HOSTKEY) {
-                       ssh->hostkey = alg->u.hostkey;
+                       ssh->hostkey = alg->u.hk.hostkey;
+                        s->warn_hk = alg->u.hk.warn;
                    } else if (i == KEXLIST_CSCIPHER) {
                        s->cscipher_tobe = alg->u.cipher.cipher;
                        s->warn_cscipher = alg->u.cipher.warn;
@@ -6685,7 +6678,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                    in_commasep_string(alg->u.comp->delayed_name, str, len))
                    s->pending_compression = TRUE;  /* try this later */
            }
-           bombout(("Couldn't agree a %s ((available: %.*s)",
+           bombout(("Couldn't agree a %s (available: %.*s)",
                     kexlist_descr[i], len, str));
            crStopV;
          matched:;
@@ -6699,13 +6692,19 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                  * host keys offered by the server which we _don't_
                  * have cached. These will be offered as cross-
                  * certification options by ssh_get_specials.
+                 *
+                 * We also count the key we're currently using for KEX
+                 * as one we've already got, because by the time this
+                 * menu becomes visible, it will be.
                  */
                 ssh->n_uncert_hostkeys = 0;
 
                 for (j = 0; j < lenof(hostkey_algs); j++) {
-                    if (in_commasep_string(hostkey_algs[j]->name, str, len) &&
+                    if (hostkey_algs[j].alg != ssh->hostkey &&
+                        in_commasep_string(hostkey_algs[j].alg->name,
+                                           str, len) &&
                         !have_ssh_host_key(ssh->savedhost, ssh->savedport,
-                                           hostkey_algs[j]->keytype)) {
+                                           hostkey_algs[j].alg->keytype)) {
                         ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
                     }
                 }
@@ -6754,6 +6753,73 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
            }
        }
 
+       if (s->warn_hk) {
+            int j, k;
+            char *betteralgs;
+
+           ssh_set_frozen(ssh, 1);
+
+            /*
+             * Change warning box wording depending on why we chose a
+             * warning-level host key algorithm. If it's because
+             * that's all we have *cached*, use the askhk mechanism,
+             * and list the host keys we could usefully cross-certify.
+             * Otherwise, use askalg for the standard wording.
+             */
+            betteralgs = NULL;
+            for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
+                const struct ssh_signkey_with_user_pref_id *hktype =
+                    &hostkey_algs[ssh->uncert_hostkeys[j]];
+                int better = FALSE;
+                for (k = 0; k < HK_MAX; k++) {
+                    int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
+                    if (id == HK_WARN) {
+                        break;
+                    } else if (id == hktype->id) {
+                        better = TRUE;
+                        break;
+                    }
+                }
+                if (better) {
+                    if (betteralgs) {
+                        char *old_ba = betteralgs;
+                        betteralgs = dupcat(betteralgs, ",",
+                                            hktype->alg->name,
+                                            (const char *)NULL);
+                        sfree(old_ba);
+                    } else {
+                        betteralgs = dupstr(hktype->alg->name);
+                    }
+                }
+            }
+            if (betteralgs) {
+                s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
+                                  betteralgs, ssh_dialog_callback, ssh);
+                sfree(betteralgs);
+            } else {
+                s->dlgret = askalg(ssh->frontend, "host key type",
+                                   ssh->hostkey->name,
+                                   ssh_dialog_callback, ssh);
+            }
+           if (s->dlgret < 0) {
+               do {
+                   crReturnV;
+                   if (pktin) {
+                       bombout(("Unexpected data from server while"
+                                " waiting for user response"));
+                       crStopV;
+                   }
+               } 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 warning", NULL,
+                              0, TRUE);
+               crStopV;
+           }
+       }
+
        if (s->warn_cscipher) {
            ssh_set_frozen(ssh, 1);
            s->dlgret = askalg(ssh->frontend,
@@ -7170,6 +7236,42 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
     if (!s->got_session_id) {
+       /*
+        * Make a note of any other host key formats that are available.
+        */
+       {
+           int i, j, nkeys = 0;
+           char *list = NULL;
+           for (i = 0; i < lenof(hostkey_algs); i++) {
+               if (hostkey_algs[i].alg == ssh->hostkey)
+                   continue;
+
+                for (j = 0; j < ssh->n_uncert_hostkeys; j++)
+                    if (ssh->uncert_hostkeys[j] == i)
+                        break;
+
+                if (j < ssh->n_uncert_hostkeys) {
+                   char *newlist;
+                   if (list)
+                       newlist = dupprintf("%s/%s", list,
+                                           hostkey_algs[i].alg->name);
+                   else
+                       newlist = dupprintf("%s", hostkey_algs[i].alg->name);
+                   sfree(list);
+                   list = newlist;
+                   nkeys++;
+               }
+           }
+           if (list) {
+               logeventf(ssh,
+                         "Server also has %s host key%s, but we "
+                         "don't know %s", list,
+                         nkeys > 1 ? "s" : "",
+                         nkeys > 1 ? "any of them" : "it");
+               sfree(list);
+           }
+       }
+
         /*
          * Authenticate remote host: verify host key. (We've already
          * checked the signature of the exchange hash.)
@@ -7221,6 +7323,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
         s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
         logevent("Storing additional host key for this host:");
         logevent(s->fingerprint);
+        sfree(s->fingerprint);
         store_host_key(ssh->savedhost, ssh->savedport,
                        ssh->hostkey->keytype, s->keystr);
         ssh->cross_certifying = FALSE;
@@ -7415,6 +7518,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
      */
     freebn(s->K);
 
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    update_specials_menu(ssh->frontend);
+
     /*
      * Key exchange is over. Loop straight back round if we have a
      * deferred rekey reason.
@@ -7523,12 +7632,30 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 }
 
 /*
- * Add data to an SSH-2 channel output buffer.
+ * Send data on an SSH channel.  In SSH-2, this involves buffering it
+ * first.
  */
-static void ssh2_add_channel_data(struct ssh_channel *c, const char *buf,
-                                 int len)
+static int ssh_send_channel_data(struct ssh_channel *c, const char *buf,
+                                  int len)
 {
-    bufchain_add(&c->v.v2.outbuffer, buf, len);
+    if (c->ssh->version == 2) {
+       bufchain_add(&c->v.v2.outbuffer, buf, len);
+       return ssh2_try_send(c);
+    } else {
+       send_packet(c->ssh, SSH1_MSG_CHANNEL_DATA,
+                   PKT_INT, c->remoteid,
+                   PKT_INT, len,
+                   PKT_DATA, buf, len,
+                   PKT_END);
+       /*
+        * In SSH-1 we can return 0 here - implying that channels are
+        * never individually throttled - because the only
+        * circumstance that can cause throttling will be the whole
+        * SSH connection backing up, in which case _everything_ will
+        * be throttled as a whole.
+        */
+       return 0;
+    }
 }
 
 /*
@@ -7613,20 +7740,23 @@ static int ssh_is_simple(Ssh ssh)
 }
 
 /*
- * Set up most of a new ssh_channel for SSH-2.
+ * Set up most of a new ssh_channel.
  */
-static void ssh2_channel_init(struct ssh_channel *c)
+static void ssh_channel_init(struct ssh_channel *c)
 {
     Ssh ssh = c->ssh;
     c->localid = alloc_channel_id(ssh);
     c->closes = 0;
     c->pending_eof = FALSE;
     c->throttling_conn = FALSE;
-    c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
-       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);
+    if (ssh->version == 2) {
+       c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+           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);
+    }
+    add234(ssh->channels, c);
 }
 
 /*
@@ -7696,6 +7826,27 @@ static struct Packet *ssh2_chanreq_init(struct ssh_channel *c,
     return pktout;
 }
 
+static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize)
+{
+    Ssh ssh = c->ssh;
+    int buflimit;
+
+    if (ssh->version == 1) {
+       buflimit = SSH1_BUFFER_LIMIT;
+    } else {
+       if (ssh_is_simple(ssh))
+           buflimit = 0;
+       else
+           buflimit = c->v.v2.locmaxwin;
+       if (bufsize < buflimit)
+           ssh2_set_window(c, buflimit - bufsize);
+    }
+    if (c->throttling_conn && bufsize <= buflimit) {
+       c->throttling_conn = 0;
+       ssh_throttle_conn(ssh, -1);
+    }
+}
+
 /*
  * Potentially enlarge the window on an SSH-2 channel.
  */
@@ -7777,25 +7928,38 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
 /*
  * Find the channel associated with a message.  If there's no channel,
  * or it's not properly open, make a noise about it and return NULL.
+ * If the channel is shared, pass the message on to downstream and
+ * also return NULL (meaning the caller should ignore this message).
  */
-static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin)
 {
     unsigned localid = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
+    int halfopen_ok;
 
+    /* Is this message OK on a half-open connection? */
+    if (ssh->version == 1)
+       halfopen_ok = (pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
+                      pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
+    else
+       halfopen_ok = (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ||
+                      pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE);
     c = find234(ssh->channels, &localid, ssh_channelfind);
-    if (!c ||
-       (c->type != CHAN_SHARING && c->halfopen &&
-         pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
-        pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+    if (!c || (c->type != CHAN_SHARING && (c->halfopen != halfopen_ok))) {
        char *buf = dupprintf("Received %s for %s channel %u",
-                             ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
-                                           pktin->type),
-                             c ? "half-open" : "nonexistent", localid);
+                             ssh_pkt_type(ssh, pktin->type),
+                             !c ? "nonexistent" :
+                             c->halfopen ? "half-open" : "open",
+                             localid);
        ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
        sfree(buf);
        return NULL;
     }
+    if (c->type == CHAN_SHARING) {
+        share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+                                  pktin->body, pktin->length);
+        return NULL;
+    }
     return c;
 }
 
@@ -7825,15 +7989,10 @@ static void ssh2_handle_winadj_response(struct ssh_channel *c,
 
 static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
 {
-    struct ssh_channel *c = ssh2_channel_msg(ssh, pktin);
+    struct ssh_channel *c = ssh_channel_msg(ssh, pktin);
     struct outstanding_channel_request *ocr;
 
     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);
@@ -7853,14 +8012,9 @@ static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
 static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
 {
     struct ssh_channel *c;
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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);
@@ -7871,81 +8025,22 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
 {
     char *data;
     int length;
+    unsigned ext_type = 0; /* 0 means not extended */
     struct ssh_channel *c;
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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 */
+    if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+       ext_type = ssh_pkt_getuint32(pktin);
     ssh_pkt_getstring(pktin, &data, &length);
     if (data) {
-       int bufsize = 0;
+       int bufsize;
        c->v.v2.locwindow -= length;
        c->v.v2.remlocwin -= length;
-       switch (c->type) {
-         case CHAN_MAINSESSION:
-           bufsize =
-               from_backend(ssh->frontend, pktin->type ==
-                            SSH2_MSG_CHANNEL_EXTENDED_DATA,
-                            data, length);
-           break;
-         case CHAN_X11:
-           bufsize = x11_send(c->u.x11.xconn, data, length);
-           break;
-         case CHAN_SOCKDATA:
-           bufsize = pfd_send(c->u.pfd.pf, data, length);
-           break;
-         case CHAN_AGENT:
-           while (length > 0) {
-               if (c->u.a.lensofar < 4) {
-                   unsigned int l = min(4 - c->u.a.lensofar,
-                                        (unsigned)length);
-                   memcpy(c->u.a.msglen + c->u.a.lensofar,
-                          data, l);
-                   data += l;
-                   length -= l;
-                   c->u.a.lensofar += l;
-               }
-               if (c->u.a.lensofar == 4) {
-                   c->u.a.totallen =
-                       4 + GET_32BIT(c->u.a.msglen);
-                   c->u.a.message = snewn(c->u.a.totallen,
-                                          unsigned char);
-                   memcpy(c->u.a.message, c->u.a.msglen, 4);
-               }
-               if (c->u.a.lensofar >= 4 && length > 0) {
-                   unsigned int l =
-                       min(c->u.a.totallen - c->u.a.lensofar,
-                           (unsigned)length);
-                   memcpy(c->u.a.message + c->u.a.lensofar,
-                          data, l);
-                   data += l;
-                   length -= l;
-                   c->u.a.lensofar += l;
-               }
-               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,
-                                   ssh_agentf_callback, c))
-                       ssh_agentf_callback(c, reply, replylen);
-                   sfree(c->u.a.message);
-                    c->u.a.message = NULL;
-                   c->u.a.lensofar = 0;
-               }
-           }
-           bufsize = 0;
-           break;
-       }
+       if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR)
+           length = 0; /* Don't do anything with unknown extended data. */
+       bufsize = ssh_channel_data(c, ext_type == SSH2_EXTENDED_DATA_STDERR,
+                                  data, length);
        /*
         * If it looks like the remote end hit the end of its window,
         * and we didn't want it to do that, think about using a
@@ -7961,8 +8056,8 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
         * need to adjust the window if the server's
         * sent excess data.
         */
-       ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
-                       c->v.v2.locmaxwin - bufsize : 0);
+       if (bufsize < c->v.v2.locmaxwin)
+           ssh2_set_window(c, c->v.v2.locmaxwin - bufsize);
        /*
         * If we're either buffering way too much data, or if we're
         * buffering anything at all and we're in "simple" mode,
@@ -8026,9 +8121,14 @@ void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...)
     sfree(buf);
 }
 
-static void ssh_channel_destroy(struct ssh_channel *c)
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel.  This converts the channel into a CHAN_ZOMBIE.
+ */
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason)
 {
     Ssh ssh = c->ssh;
+    char const *msg = NULL;
 
     switch (c->type) {
       case CHAN_MAINSESSION:
@@ -8036,19 +8136,33 @@ static void ssh_channel_destroy(struct ssh_channel *c)
         update_specials_menu(ssh->frontend);
         break;
       case CHAN_X11:
-        if (c->u.x11.xconn != NULL)
-            x11_close(c->u.x11.xconn);
-        logevent("Forwarded X11 connection terminated");
+        assert(c->u.x11.xconn != NULL);
+       x11_close(c->u.x11.xconn);
+        msg = "Forwarded X11 connection terminated";
         break;
       case CHAN_AGENT:
         sfree(c->u.a.message);
         break;
       case CHAN_SOCKDATA:
-        if (c->u.pfd.pf != NULL)
-            pfd_close(c->u.pfd.pf);
-        logevent("Forwarded port closed");
+        assert(c->u.pfd.pf != NULL);
+       pfd_close(c->u.pfd.pf);
+       msg = "Forwarded port closed";
         break;
     }
+    c->type = CHAN_ZOMBIE;
+    if (msg != NULL) {
+       if (reason != NULL)
+           logeventf(ssh, "%s %s", msg, reason);
+       else
+           logevent(msg);
+    }
+}
+
+static void ssh_channel_destroy(struct ssh_channel *c)
+{
+    Ssh ssh = c->ssh;
+
+    ssh_channel_close_local(c, NULL);
 
     del234(ssh->channels, c);
     if (ssh->version == 2) {
@@ -8069,6 +8183,7 @@ static void ssh2_channel_check_close(struct ssh_channel *c)
     Ssh ssh = c->ssh;
     struct Packet *pktout;
 
+    assert(ssh->version == 2);
     if (c->halfopen) {
         /*
          * If we've sent out our own CHANNEL_OPEN but not yet seen
@@ -8104,13 +8219,14 @@ static void ssh2_channel_check_close(struct ssh_channel *c)
     }
 }
 
-static void ssh2_channel_got_eof(struct ssh_channel *c)
+static void ssh_channel_got_eof(struct ssh_channel *c)
 {
     if (c->closes & CLOSES_RCVD_EOF)
         return;                        /* already seen EOF */
     c->closes |= CLOSES_RCVD_EOF;
 
     if (c->type == CHAN_X11) {
+       assert(c->u.x11.xconn != NULL);
        x11_send_eof(c->u.x11.xconn);
     } else if (c->type == CHAN_AGENT) {
         if (c->u.a.outstanding_requests == 0) {
@@ -8118,6 +8234,7 @@ static void ssh2_channel_got_eof(struct ssh_channel *c)
             sshfwd_write_eof(c);
         }
     } else if (c->type == CHAN_SOCKDATA) {
+       assert(c->u.pfd.pf != NULL);
        pfd_send_eof(c->u.pfd.pf);
     } else if (c->type == CHAN_MAINSESSION) {
         Ssh ssh = c->ssh;
@@ -8136,43 +8253,32 @@ static void ssh2_channel_got_eof(struct ssh_channel *c)
         }
         ssh->sent_console_eof = TRUE;
     }
-
-    ssh2_channel_check_close(c);
 }
 
 static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
 {
     struct ssh_channel *c;
 
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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);
+    ssh_channel_got_eof(c);
+    ssh2_channel_check_close(c);
 }
 
 static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
 {
     struct ssh_channel *c;
 
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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
      * implied EOF if we haven't seen EOF yet.
      */
-    ssh2_channel_got_eof(c);
+    ssh_channel_got_eof(c);
 
     if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
         /*
@@ -8241,24 +8347,18 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
 {
     struct ssh_channel *c;
 
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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;
-    }
-    assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+    assert(c->halfopen); /* ssh_channel_msg will have enforced this */
     c->remoteid = ssh_pkt_getuint32(pktin);
     c->halfopen = FALSE;
     c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
     c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
 
-    if (c->type == CHAN_SOCKDATA_DORMANT) {
-        c->type = CHAN_SOCKDATA;
-        if (c->u.pfd.pf)
-            pfd_confirm(c->u.pfd.pf);
+    if (c->type == CHAN_SOCKDATA) {
+       assert(c->u.pfd.pf != NULL);
+       pfd_confirm(c->u.pfd.pf);
     } else if (c->type == CHAN_ZOMBIE) {
         /*
          * This case can occur if a local socket error occurred
@@ -8301,17 +8401,12 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
     int reason_length;
     struct ssh_channel *c;
 
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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;
-    }
-    assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+    assert(c->halfopen); /* ssh_channel_msg will have enforced this */
 
-    if (c->type == CHAN_SOCKDATA_DORMANT) {
+    if (c->type == CHAN_SOCKDATA) {
         reason_code = ssh_pkt_getuint32(pktin);
         if (reason_code >= lenof(reasons))
             reason_code = 0; /* ensure reasons[reason_code] in range */
@@ -8354,14 +8449,9 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
     struct ssh_channel *c;
     struct Packet *pktout;
 
-    c = ssh2_channel_msg(ssh, pktin);
+    c = ssh_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);
 
@@ -8716,14 +8806,13 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        logeventf(ssh, "Rejected channel open: %s", error);
        sfree(c);
     } else {
-       ssh2_channel_init(c);
+       ssh_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);
        ssh2_pkt_adduint32(pktout, c->localid);
@@ -8788,14 +8877,13 @@ static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
 }
 
 /* Helper function to deal with sending tty modes for "pty-req" */
-static void ssh2_send_ttymode(void *data, char *mode, char *val)
+static void ssh2_send_ttymode(void *data,
+                              const struct ssh_ttymode *mode, char *val)
 {
     struct Packet *pktout = (struct Packet *)data;
-    int i = 0;
     unsigned int arg = 0;
-    while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
-    if (i == lenof(ssh_ttymodes)) return;
-    switch (ssh_ttymodes[i].type) {
+
+    switch (mode->type) {
       case TTY_OP_CHAR:
        arg = ssh_tty_parse_specchar(val);
        break;
@@ -8803,7 +8891,7 @@ static void ssh2_send_ttymode(void *data, char *mode, char *val)
        arg = ssh_tty_parse_boolean(val);
        break;
     }
-    ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+    ssh2_pkt_addbyte(pktout, mode->opcode);
     ssh2_pkt_adduint32(pktout, arg);
 }
 
@@ -9050,7 +9138,6 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
        int pklen, alglen, commentlen;
        int siglen, retlen, len;
        char *q, *agentreq, *ret;
-       int try_send;
        struct Packet *pktout;
        Filename *keyfile;
 #ifndef NO_GSSAPI
@@ -10506,7 +10593,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
     } else {
        ssh->mainchan = snew(struct ssh_channel);
        ssh->mainchan->ssh = ssh;
-       ssh2_channel_init(ssh->mainchan);
+       ssh_channel_init(ssh->mainchan);
 
        if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
            /*
@@ -10539,7 +10626,6 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
        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 main channel");
     }
@@ -10703,7 +10789,6 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
        ssh->send_ok = 1;
     while (1) {
        crReturnV;
-       s->try_send = FALSE;
        if (pktin) {
 
            /*
@@ -10718,18 +10803,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
            /*
             * We have spare data. Add it to the channel buffer.
             */
-           ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
-           s->try_send = TRUE;
-       }
-       if (s->try_send) {
-           int i;
-           struct ssh_channel *c;
-           /*
-            * Try to send data on all channels if we can.
-            */
-           for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
-                if (c->type != CHAN_SHARING)
-                    ssh2_try_send_and_unthrottle(ssh, c);
+           ssh_send_channel_data(ssh->mainchan, (char *)in, inlen);
        }
     }
 
@@ -11155,17 +11229,7 @@ static void ssh_free(void *handle)
 
     if (ssh->channels) {
        while ((c = delpos234(ssh->channels, 0)) != NULL) {
-           switch (c->type) {
-             case CHAN_X11:
-               if (c->u.x11.xconn != NULL)
-                   x11_close(c->u.x11.xconn);
-               break;
-             case CHAN_SOCKDATA:
-             case CHAN_SOCKDATA_DORMANT:
-               if (c->u.pfd.pf != NULL)
-                   pfd_close(c->u.pfd.pf);
-               break;
-           }
+           ssh_channel_close_local(c, NULL);
            if (ssh->version == 2) {
                struct outstanding_channel_request *ocr, *nocr;
                ocr = c->v.v2.chanreq_head;
@@ -11467,7 +11531,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
             for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
                 struct telnet_special uncert[1];
                 const struct ssh_signkey *alg =
-                    hostkey_algs[ssh->uncert_hostkeys[i]];
+                    hostkey_algs[ssh->uncert_hostkeys[i]].alg;
                 uncert[0].name = alg->name;
                 uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
                 ADD_SPECIALS(uncert);
@@ -11535,7 +11599,7 @@ static void ssh_special(void *handle, Telnet_Special code)
            do_ssh2_transport(ssh, "at user request", -1, NULL);
        }
     } else if (code >= TS_LOCALSTART) {
-        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART];
+        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
         ssh->cross_certifying = TRUE;
        if (!ssh->kex_in_progress && !ssh->bare_connection &&
             ssh->version == 2) {
@@ -11590,11 +11654,10 @@ void *new_sock_channel(void *handle, struct PortForwarding *pf)
     c = snew(struct ssh_channel);
 
     c->ssh = ssh;
-    ssh2_channel_init(c);
+    ssh_channel_init(c);
     c->halfopen = TRUE;
-    c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+    c->type = CHAN_SOCKDATA;/* identify channel type */
     c->u.pfd.pf = pf;
-    add234(ssh->channels, c);
     return c;
 }
 
@@ -11604,10 +11667,9 @@ unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx)
     c = snew(struct ssh_channel);
 
     c->ssh = ssh;
-    ssh2_channel_init(c);
+    ssh_channel_init(c);
     c->type = CHAN_SHARING;
     c->u.sharing.ctx = sharing_ctx;
-    add234(ssh->channels, c);
     return c->localid;
 }
 
@@ -11640,7 +11702,6 @@ void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
 static void ssh_unthrottle(void *handle, int bufsize)
 {
     Ssh ssh = (Ssh) handle;
-    int buflimit;
 
     if (ssh->version == 1) {
        if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
@@ -11648,19 +11709,8 @@ static void ssh_unthrottle(void *handle, int bufsize)
            ssh_throttle_conn(ssh, -1);
        }
     } else {
-       if (ssh->mainchan) {
-           ssh2_set_window(ssh->mainchan,
-                           bufsize < ssh->mainchan->v.v2.locmaxwin ?
-                           ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
-           if (ssh_is_simple(ssh))
-               buflimit = 0;
-           else
-               buflimit = ssh->mainchan->v.v2.locmaxwin;
-           if (ssh->mainchan->throttling_conn && bufsize <= buflimit) {
-               ssh->mainchan->throttling_conn = 0;
-               ssh_throttle_conn(ssh, -1);
-           }
-       }
+       if (ssh->mainchan)
+           ssh_channel_unthrottle(ssh->mainchan, bufsize);
     }
 
     /*