Explorar o código

Merge branch 'thirdparty_dev' into dev

# Conflicts:
#	source/putty/ssh.c
#	source/putty/sshcommon.c

Source commit: 37f7c05fbeffd3fbc4f1501ff3a57e4e436dd3d2
Martin Prikryl %!s(int64=6) %!d(string=hai) anos
pai
achega
1e84e2caf3

+ 1 - 0
source/putty/defs.h

@@ -95,6 +95,7 @@ typedef struct ptrlen {
 typedef struct logblank_t logblank_t;
 
 typedef struct BinaryPacketProtocol BinaryPacketProtocol;
+typedef struct PacketProtocolLayer PacketProtocolLayer;
 
 /* Do a compile-time type-check of 'to_check' (without evaluating it),
  * as a side effect of returning the value 'to_return'. Note that

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 347 - 948
source/putty/ssh.c


+ 51 - 5
source/putty/ssh.h

@@ -187,7 +187,18 @@ void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
                              int protomajor, int protominor,
                              const void *initial_data, int initial_len);
 
-struct ssh_rportfwd;
+/* Structure definition centralised here because the SSH-1 and SSH-2
+ * connection layers both use it. But the client module (portfwd.c)
+ * should not try to look inside here. */
+struct ssh_rportfwd {
+    unsigned sport, dport;
+    char *shost, *dhost;
+    int addressfamily;
+    char *log_description; /* name of remote listening port, for logging */
+    ssh_sharing_connstate *share_ctx;
+    PortFwdRecord *pfr;
+};
+void free_rportfwd(struct ssh_rportfwd *rpf);
 
 struct ConnectionLayerVtable {
     /* Allocate and free remote-to-local port forwardings, called by
@@ -231,6 +242,25 @@ struct ConnectionLayerVtable {
 
     /* Query whether the connection layer is doing agent forwarding */
     int (*agent_forwarding_permitted)(ConnectionLayer *cl);
+
+    /* Set the size of the main terminal window (if any) */
+    void (*terminal_size)(ConnectionLayer *cl, int width, int height);
+
+    /* Indicate that the backlog on standard output has cleared */
+    void (*stdout_unthrottle)(ConnectionLayer *cl, int bufsize);
+
+    /* Query the size of the backlog on standard _input_ */
+    int (*stdin_backlog)(ConnectionLayer *cl);
+
+    /* Tell the connection layer that the SSH connection itself has
+     * backed up, so it should tell all currently open channels to
+     * cease reading from their local input sources if they can. (Or
+     * tell it that that state of affairs has gone away again.) */
+    void (*throttle_all_channels)(ConnectionLayer *cl, int throttled);
+
+    /* Ask the connection layer about its current preference for
+     * line-discipline options. */
+    int (*ldisc_option)(ConnectionLayer *cl, int option);
 };
 
 struct ConnectionLayer {
@@ -257,6 +287,13 @@ struct ConnectionLayer {
     ((cl)->vt->sharing_queue_global_request(cl, cs))
 #define ssh_agent_forwarding_permitted(cl) \
     ((cl)->vt->agent_forwarding_permitted(cl))
+#define ssh_terminal_size(cl, w, h) ((cl)->vt->terminal_size(cl, w, h))
+#define ssh_stdout_unthrottle(cl, bufsize) \
+    ((cl)->vt->stdout_unthrottle(cl, bufsize))
+#define ssh_stdin_backlog(cl) ((cl)->vt->stdin_backlog(cl))
+#define ssh_throttle_all_channels(cl, throttled) \
+    ((cl)->vt->throttle_all_channels(cl, throttled))
+#define ssh_ldisc_option(cl, option) ((cl)->vt->ldisc_option(cl, option))
 
 /* Exports from portfwd.c */
 PortFwdManager *portfwdmgr_new(ConnectionLayer *cl);
@@ -270,6 +307,19 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret,
 
 Frontend *ssh_get_frontend(Ssh ssh);
 
+/* Communications back to ssh.c from connection layers */
+void ssh_throttle_conn(Ssh ssh, int adjust);
+void ssh_got_exitcode(Ssh ssh, int status);
+void ssh_ldisc_update(Ssh ssh);
+void ssh_got_fallback_cmd(Ssh ssh);
+
+/* Functions to abort the connection, for various reasons. */
+void ssh_remote_error(Ssh ssh, const char *fmt, ...);
+void ssh_remote_eof(Ssh ssh, const char *fmt, ...);
+void ssh_proto_error(Ssh ssh, const char *fmt, ...);
+void ssh_sw_abort(Ssh ssh, const char *fmt, ...);
+void ssh_user_close(Ssh ssh, const char *fmt, ...);
+
 #define SSH_CIPHER_IDEA		1
 #define SSH_CIPHER_DES		2
 #define SSH_CIPHER_3DES		3
@@ -1268,10 +1318,6 @@ enum {
 #undef DEF_ENUM_UNIVERSAL
 #undef DEF_ENUM_CONTEXTUAL
 
-/* Given that virtual packet types exist, this is how big the dispatch
- * table has to be */
-#define SSH_MAX_MSG                           0x101
-
 /*
  * SSH-1 agent messages.
  */

+ 21 - 14
source/putty/ssh1bpp.c

@@ -89,8 +89,11 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
 
 #define BPP_READ(ptr, len) do                                   \
     {                                                           \
-        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+        crMaybeWaitUntilV(s->bpp.input_eof ||                   \
+                          bufchain_try_fetch_consume(           \
                               s->bpp.in_raw, ptr, len));        \
+        if (s->bpp.input_eof)                                   \
+            goto eof;                                           \
     } while (0)
 
 static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
@@ -110,9 +113,9 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
         }
 
         if (s->len < 0 || s->len > 262144) { /* SSH1.5-mandated max size */
-            s->bpp.error = dupprintf(
-                "Extremely large packet length from server suggests"
-                " data stream corruption");
+            ssh_sw_abort(s->bpp.ssh,
+                         "Extremely large packet length from server suggests"
+                         " data stream corruption");
             crStopV;
         }
 
@@ -135,8 +138,8 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
 
         if (s->cipher && detect_attack(s->crcda_ctx,
                                        s->data, s->biglen, NULL)) {
-            s->bpp.error = dupprintf(
-                "Network attack (CRC compensation) detected!");
+            ssh_sw_abort(s->bpp.ssh,
+                         "Network attack (CRC compensation) detected!");
             crStopV;
         }
 
@@ -146,8 +149,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
         s->realcrc = crc32_compute(s->data, s->biglen - 4);
         s->gotcrc = GET_32BIT(s->data + s->biglen - 4);
         if (s->gotcrc != s->realcrc) {
-            s->bpp.error = dupprintf(
-                "Incorrect CRC received on packet");
+            ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet");
             crStopV;
         }
 
@@ -157,8 +159,8 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
             if (!ssh_decompressor_decompress(
                     s->decompctx, s->data + s->pad, s->length + 1,
                     &decompblk, &decomplen)) {
-                s->bpp.error = dupprintf(
-                    "Zlib decompression encountered invalid data");
+                ssh_sw_abort(s->bpp.ssh,
+                             "Zlib decompression encountered invalid data");
                 crStopV;
             }
 
@@ -205,10 +207,6 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
             s->pktin = NULL;
 
             switch (type) {
-              case SSH1_MSG_DISCONNECT:
-                s->bpp.seen_disconnect = TRUE;
-                break;
-
               case SSH1_SMSG_SUCCESS:
               case SSH1_SMSG_FAILURE:
                 if (s->pending_compression_request) {
@@ -240,6 +238,15 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
             }
         }
     }
+
+  eof:
+    if (!s->bpp.expect_close) {
+        ssh_remote_error(s->bpp.ssh,
+                         "Server unexpectedly closed network connection");
+    } else {
+        ssh_remote_eof(s->bpp.ssh, "Server closed network connection");
+    }
+
     crFinishV;
 }
 

+ 1211 - 0
source/putty/ssh1connection.c

@@ -0,0 +1,1211 @@
+/*
+ * Packet protocol layer for the SSH-1 'connection protocol', i.e.
+ * everything after authentication finishes.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshchan.h"
+#include "sshcr.h"
+
+struct ssh1_channel;
+
+struct outstanding_succfail;
+
+struct ssh1_connection_state {
+    int crState;
+
+    Ssh ssh;
+
+    Conf *conf;
+    int local_protoflags;
+
+    tree234 *channels;		       /* indexed by local id */
+
+    int got_pty;
+    int echoedit;
+    int ospeed, ispeed;
+    int stdout_throttling;
+    int session_ready;
+    int session_eof_pending, session_eof_sent, session_terminated;
+    int term_width, term_height, term_width_orig, term_height_orig;
+
+    int X11_fwd_enabled;
+    struct X11Display *x11disp;
+    struct X11FakeAuth *x11auth;
+    tree234 *x11authtree;
+
+    int agent_fwd_enabled;
+
+    tree234 *rportfwds;
+    PortFwdManager *portfwdmgr;
+    int portfwdmgr_configured;
+
+    int finished_setup;
+
+    /*
+     * These store the list of requests that we're waiting for
+     * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't
+     * come with any indication of what they're in response to, so we
+     * have to keep track of the queue ourselves.)
+     */
+    struct outstanding_succfail *succfail_head, *succfail_tail;
+
+    ConnectionLayer cl;
+    PacketProtocolLayer ppl;
+};
+
+static int ssh1_rportfwd_cmp(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->dhost, b->dhost)) != 0)
+	return i < 0 ? -1 : +1;
+    if (a->dport > b->dport)
+	return +1;
+    if (a->dport < b->dport)
+	return -1;
+    return 0;
+}
+
+static void ssh1_connection_free(PacketProtocolLayer *); 
+static void ssh1_connection_process_queue(PacketProtocolLayer *);
+static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg);
+static int ssh1_connection_want_user_input(PacketProtocolLayer *ppl);
+static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl);
+static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const struct PacketProtocolLayerVtable ssh1_connection_vtable = {
+    ssh1_connection_free,
+    ssh1_connection_process_queue,
+    ssh1_common_get_specials,
+    ssh1_connection_special_cmd,
+    ssh1_connection_want_user_input,
+    ssh1_connection_got_user_input,
+    ssh1_connection_reconfigure,
+    NULL /* no layer names in SSH-1 */,
+};
+
+static struct ssh_rportfwd *ssh1_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx);
+static void ssh1_rportfwd_remove(
+    ConnectionLayer *cl, struct ssh_rportfwd *rpf);
+static SshChannel *ssh1_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *org, Channel *chan);
+static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
+static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height);
+static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize);
+static int ssh1_stdin_backlog(ConnectionLayer *cl);
+static void ssh1_throttle_all_channels(ConnectionLayer *cl, int throttled);
+static int ssh1_ldisc_option(ConnectionLayer *cl, int option);
+
+static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
+    ssh1_rportfwd_alloc,
+    ssh1_rportfwd_remove,
+    ssh1_lportfwd_open,
+    NULL /* add_sharing_x11_display */,
+    NULL /* remove_sharing_x11_display */,
+    NULL /* send_packet_from_downstream */,
+    NULL /* alloc_sharing_channel */,
+    NULL /* delete_sharing_channel */,
+    NULL /* sharing_queue_global_request */,
+    ssh1_agent_forwarding_permitted,
+    ssh1_terminal_size,
+    ssh1_stdout_unthrottle,
+    ssh1_stdin_backlog,
+    ssh1_throttle_all_channels,
+    ssh1_ldisc_option,
+};
+
+struct ssh1_channel {
+    struct ssh1_connection_state *connlayer;
+
+    unsigned remoteid, localid;
+    int type;
+    /* True if we opened this channel but server hasn't confirmed. */
+    int halfopen;
+
+    /* Bitmap of whether we've sent/received CHANNEL_CLOSE and
+     * CHANNEL_CLOSE_CONFIRMATION. */
+#define CLOSES_SENT_CLOSE      1
+#define CLOSES_SENT_CLOSECONF  2
+#define CLOSES_RCVD_CLOSE      4
+#define CLOSES_RCVD_CLOSECONF  8
+    int closes;
+
+    /*
+     * This flag indicates that an EOF is pending on the outgoing side
+     * of the channel: that is, wherever we're getting the data for
+     * this channel has sent us some data followed by EOF. We can't
+     * actually send the EOF until we've finished sending the data, so
+     * we set this flag instead to remind us to do so once our buffer
+     * is clear.
+     */
+    int pending_eof;
+
+    /*
+     * True if this channel is causing the underlying connection to be
+     * throttled.
+     */
+    int throttling_conn;
+
+    /*
+     * True if we currently have backed-up data on the direction of
+     * this channel pointing out of the SSH connection, and therefore
+     * would prefer the 'Channel' implementation not to read further
+     * local input if possible.
+     */
+    int throttled_by_backlog;
+
+    Channel *chan;      /* handle the client side of this channel, if not */
+    SshChannel sc;      /* entry point for chan to talk back to */
+};
+
+static int ssh1channel_write(SshChannel *c, const void *buf, int len);
+static void ssh1channel_write_eof(SshChannel *c);
+static void ssh1channel_unclean_close(SshChannel *c, const char *err);
+static void ssh1channel_unthrottle(SshChannel *c, int bufsize);
+static Conf *ssh1channel_get_conf(SshChannel *c);
+
+static const struct SshChannelVtable ssh1channel_vtable = {
+    ssh1channel_write,
+    ssh1channel_write_eof,
+    ssh1channel_unclean_close,
+    ssh1channel_unthrottle,
+    ssh1channel_get_conf,
+    NULL /* window_override_removed is only used by SSH-2 sharing */,
+    NULL /* x11_sharing_handover, likewise */,
+};
+
+static void ssh1_channel_init(struct ssh1_channel *c);
+static void ssh1_channel_try_eof(struct ssh1_channel *c);
+static void ssh1_channel_close_local(struct ssh1_channel *c,
+                                     const char *reason);
+static void ssh1_channel_destroy(struct ssh1_channel *c);
+static void ssh1_channel_check_close(struct ssh1_channel *c);
+
+static int ssh1_check_termination(struct ssh1_connection_state *s);
+
+typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s,
+                                PktIn *pktin, void *ctx);
+struct outstanding_succfail {
+    sf_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_succfail *next;
+};
+static void ssh1_queue_succfail_handler(
+    struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx)
+{
+    struct outstanding_succfail *osf =
+        snew(struct outstanding_succfail);
+    osf->handler = handler;
+    osf->ctx = ctx;
+    if (s->succfail_tail)
+        s->succfail_tail->next = osf;
+    else
+        s->succfail_head = osf;
+    s->succfail_tail = osf;
+}
+
+static int ssh1_channelcmp(void *av, void *bv)
+{
+    const struct ssh1_channel *a = (const struct ssh1_channel *) av;
+    const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
+    if (a->localid < b->localid)
+	return -1;
+    if (a->localid > b->localid)
+	return +1;
+    return 0;
+}
+
+static int ssh1_channelfind(void *av, void *bv)
+{
+    const unsigned *a = (const unsigned *) av;
+    const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
+    if (*a < b->localid)
+	return -1;
+    if (*a > b->localid)
+	return +1;
+    return 0;
+}
+
+static void ssh1_channel_free(struct ssh1_channel *c)
+{
+    if (c->chan)
+        chan_free(c->chan);
+    sfree(c);
+}
+
+PacketProtocolLayer *ssh1_connection_new(
+    Ssh ssh, Conf *conf, ConnectionLayer **cl_out)
+{
+    struct ssh1_connection_state *s = snew(struct ssh1_connection_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh1_connection_vtable;
+
+    s->conf = conf_copy(conf);
+
+    s->channels = newtree234(ssh1_channelcmp);
+
+    s->x11authtree = newtree234(x11_authcmp);
+
+    /* Need to get the frontend for s->cl now, because we won't be
+     * helpfully notified when a copy is written into s->ppl by our
+     * owner. */
+    s->cl.vt = &ssh1_connlayer_vtable;
+    s->cl.frontend = ssh_get_frontend(ssh);
+
+    s->portfwdmgr = portfwdmgr_new(&s->cl);
+    s->rportfwds = newtree234(ssh1_rportfwd_cmp);
+
+    *cl_out = &s->cl;
+    return &s->ppl;
+}
+
+static void ssh1_connection_free(PacketProtocolLayer *ppl)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    struct X11FakeAuth *auth;
+    struct ssh1_channel *c;
+    struct ssh_rportfwd *rpf;
+
+    conf_free(s->conf);
+
+    while ((c = delpos234(s->channels, 0)) != NULL)
+        ssh1_channel_free(c);
+    freetree234(s->channels);
+
+    if (s->x11disp)
+	x11_free_display(s->x11disp);
+    while ((auth = delpos234(s->x11authtree, 0)) != NULL)
+        x11_free_fake_auth(auth);
+    freetree234(s->x11authtree);
+
+    while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+        free_rportfwd(rpf);
+    freetree234(s->rportfwds);
+    portfwdmgr_free(s->portfwdmgr);
+
+    sfree(s);
+}
+
+void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags)
+{
+    assert(ppl->vt == &ssh1_connection_vtable);
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    s->local_protoflags = flags;
+}
+
+static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)
+{
+    PktIn *pktin;
+    PktOut *pktout;
+    ptrlen data, host;
+    struct ssh1_channel *c;
+    unsigned localid, remid;
+    int port, expect_halfopen;
+    struct ssh_rportfwd pf, *pfp;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    /* Cross-reference to ssh1login.c to handle the common packets
+     * between login and connection: DISCONNECT, DEBUG and IGNORE. */
+    extern int ssh1_common_filter_queue(PacketProtocolLayer *ppl);
+
+    while (1) {
+        if (ssh1_common_filter_queue(&s->ppl))
+            return TRUE;
+        if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+            return FALSE;
+
+        switch (pktin->type) {
+          case SSH1_SMSG_SUCCESS:
+          case SSH1_SMSG_FAILURE:
+            if (!s->finished_setup) {
+                /* During initial setup, these messages are not
+                 * filtered out, but go back to the main coroutine. */
+                return FALSE;
+            }
+
+            if (!s->succfail_head) {
+                ssh_remote_error(s->ppl.ssh,
+                                 "Received %s with no outstanding request",
+                                 ssh1_pkt_type(pktin->type));
+                return TRUE;
+            }
+
+            s->succfail_head->handler(s, pktin, s->succfail_head->ctx);
+            {
+                struct outstanding_succfail *tmp = s->succfail_head;
+                s->succfail_head = s->succfail_head->next;
+                sfree(tmp);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_SMSG_X11_OPEN:
+            remid = get_uint32(pktin);
+
+            /* Refuse if X11 forwarding is disabled. */
+            if (!s->X11_fwd_enabled) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+                put_uint32(pktout, remid);
+                pq_push(s->ppl.out_pq, pktout);
+                ppl_logevent(("Rejected X11 connect request"));
+            } else {
+                c = snew(struct ssh1_channel);
+                c->connlayer = s;
+                ssh1_channel_init(c);
+                c->remoteid = remid;
+                c->chan = x11_new_channel(s->x11authtree, &c->sc,
+                                          NULL, -1, FALSE);
+                c->remoteid = remid;
+                c->halfopen = FALSE;
+
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, c->localid);
+                pq_push(s->ppl.out_pq, pktout);
+                ppl_logevent(("Opened X11 forward channel"));
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_SMSG_AGENT_OPEN:
+            remid = get_uint32(pktin);
+
+            /* Refuse if agent forwarding is disabled. */
+            if (!s->agent_fwd_enabled) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+                put_uint32(pktout, remid);
+                pq_push(s->ppl.out_pq, pktout);
+            } else {
+                c = snew(struct ssh1_channel);
+                c->connlayer = s;
+                ssh1_channel_init(c);
+                c->remoteid = remid;
+                c->chan = agentf_new(&c->sc);
+                c->halfopen = FALSE;
+
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, c->localid);
+                pq_push(s->ppl.out_pq, pktout);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_MSG_PORT_OPEN:
+            remid = get_uint32(pktin);
+            host = get_string(pktin);
+            port = toint(get_uint32(pktin));
+
+            pf.dhost = mkstr(host);
+            pf.dport = port;
+            pfp = find234(s->rportfwds, &pf, NULL);
+
+            if (!pfp) {
+                ppl_logevent(("Rejected remote port open request for %s:%d",
+                              pf.dhost, port));
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+                put_uint32(pktout, remid);
+                pq_push(s->ppl.out_pq, pktout);
+            } else {
+                char *err;
+
+                c = snew(struct ssh1_channel);
+                c->connlayer = s;
+                ppl_logevent(("Received remote port open request for %s:%d",
+                              pf.dhost, port));
+                err = portfwdmgr_connect(
+                    s->portfwdmgr, &c->chan, pf.dhost, port,
+                    &c->sc, pfp->addressfamily);
+
+                if (err) {
+                    ppl_logevent(("Port open failed: %s", err));
+                    sfree(err);
+                    ssh1_channel_free(c);
+                    pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+                    put_uint32(pktout, remid);
+                    pq_push(s->ppl.out_pq, pktout);
+                } else {
+                    ssh1_channel_init(c);
+                    c->remoteid = remid;
+                    c->halfopen = FALSE;
+                    pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+                    put_uint32(pktout, c->remoteid);
+                    put_uint32(pktout, c->localid);
+                    pq_push(s->ppl.out_pq, pktout);
+                    ppl_logevent(("Forwarded port opened successfully"));
+                }
+            }
+
+            sfree(pf.dhost);
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_MSG_CHANNEL_DATA:
+          case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
+          case SSH1_MSG_CHANNEL_OPEN_FAILURE:
+          case SSH1_MSG_CHANNEL_CLOSE:
+          case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
+            /*
+             * Common preliminary code for all the messages from the
+             * server that cite one of our channel ids: look up that
+             * channel id, check it exists, and if it's for a sharing
+             * downstream, pass it on.
+             */
+            localid = get_uint32(pktin);
+            c = find234(s->channels, &localid, ssh1_channelfind);
+
+            expect_halfopen = (
+                pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
+                pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
+
+            if (!c || c->halfopen != expect_halfopen) {
+                ssh_remote_error(
+                    s->ppl.ssh, "Received %s for %s channel %u",
+                    ssh1_pkt_type(pktin->type),
+                    !c ? "nonexistent" : c->halfopen ? "half-open" : "open",
+                    localid);
+                return TRUE;
+            }
+ 
+            switch (pktin->type) {
+              case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
+                assert(c->halfopen);
+                c->remoteid = get_uint32(pktin);
+                c->halfopen = FALSE;
+                c->throttling_conn = FALSE;
+
+                chan_open_confirmation(c->chan);
+
+                /*
+                 * Now that the channel is fully open, it's possible
+                 * in principle to immediately close it. Check whether
+                 * it wants us to!
+                 *
+                 * This can occur if a local socket error occurred
+                 * between us sending out CHANNEL_OPEN and receiving
+                 * OPEN_CONFIRMATION. If that happens, all we can do
+                 * is immediately initiate close proceedings now that
+                 * we know the server's id to put in the close
+                 * message. We'll have handled that in this code by
+                 * having already turned c->chan into a zombie, so its
+                 * want_close method (which ssh1_channel_check_close
+                 * will consult) will already be returning TRUE.
+                 */
+                ssh1_channel_check_close(c);
+
+                if (c->pending_eof)
+                    ssh1_channel_try_eof(c); /* in case we had a pending EOF */
+                break;
+
+              case SSH1_MSG_CHANNEL_OPEN_FAILURE:
+                assert(c->halfopen);
+
+                chan_open_failed(c->chan, NULL);
+                chan_free(c->chan);
+
+                del234(s->channels, c);
+                ssh1_channel_free(c);
+                break;
+
+              case SSH1_MSG_CHANNEL_DATA:
+                data = get_string(pktin);
+                if (!get_err(pktin)) {
+                    int bufsize = chan_send(
+                        c->chan, FALSE, data.ptr, data.len);
+
+                    if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
+                        c->throttling_conn = TRUE;
+                        ssh_throttle_conn(s->ppl.ssh, +1);
+                    }
+                }
+                break;
+
+              case SSH1_MSG_CHANNEL_CLOSE:
+                if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+                    c->closes |= CLOSES_RCVD_CLOSE;
+                    chan_send_eof(c->chan);
+                    ssh1_channel_check_close(c);
+                }
+                break;
+
+              case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
+                if (!(c->closes & CLOSES_RCVD_CLOSECONF)) {
+                    if (!(c->closes & CLOSES_SENT_CLOSE)) {
+                        ssh_remote_error(
+                            s->ppl.ssh,
+                            "Received CHANNEL_CLOSE_CONFIRMATION for channel"
+                            " %u for which we never sent CHANNEL_CLOSE\n",
+                            c->localid);
+                        return TRUE;
+                    }
+
+                    c->closes |= CLOSES_RCVD_CLOSECONF;
+                    ssh1_channel_check_close(c);
+                }
+                break;
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_SMSG_STDOUT_DATA:
+          case SSH1_SMSG_STDERR_DATA:
+            data = get_string(pktin);
+            if (!get_err(pktin)) {
+                int bufsize = from_backend(
+                    s->ppl.frontend, pktin->type == SSH1_SMSG_STDERR_DATA,
+                    data.ptr, data.len);
+                if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+                    s->stdout_throttling = 1;
+                    ssh_throttle_conn(s->ppl.ssh, +1);
+                }
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH1_SMSG_EXIT_STATUS:
+            {
+                int exitcode = get_uint32(pktin);
+                ppl_logevent(("Server sent command exit status %d", exitcode));
+                ssh_got_exitcode(s->ppl.ssh, exitcode);
+
+                s->session_terminated = TRUE;
+                if (ssh1_check_termination(s))
+                    return TRUE;
+            }
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          default:
+            return FALSE;
+        }
+    }
+}
+
+static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s)
+{
+    ssh1_connection_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    PktIn *pktin;
+    PktOut *pktout;
+
+    if (ssh1_connection_filter_queue(s)) /* no matter why we were called */
+        return;
+
+    crBegin(s->crState);
+
+    if (ssh_agent_forwarding_permitted(&s->cl)) {
+	ppl_logevent(("Requesting agent forwarding"));
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp,
+                                    SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+        pq_push(s->ppl.out_pq, pktout);
+        crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
+	if (pktin->type == SSH1_SMSG_SUCCESS) {
+	    ppl_logevent(("Agent forwarding enabled"));
+	    s->agent_fwd_enabled = TRUE;
+	} else if (pktin->type == SSH1_SMSG_FAILURE) {
+	    ppl_logevent(("Agent forwarding refused"));
+	} else {
+            ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
+                            " in response to agent forwarding request, "
+                            "type %d (%s)", pktin->type,
+                            ssh1_pkt_type(pktin->type));
+            return;
+	}
+    }
+
+    if (conf_get_int(s->conf, CONF_x11_forward)) {
+        s->x11disp =
+            x11_setup_display(conf_get_str(s->conf, CONF_x11_display),
+                              s->conf);
+        if (!s->x11disp) {
+            /* FIXME: return an error message from x11_setup_display */
+            ppl_logevent(("X11 forwarding not enabled: unable to"
+                          " initialise X display"));
+        } else {
+            s->x11auth = x11_invent_fake_auth
+                (s->x11authtree, conf_get_int(s->conf, CONF_x11_auth));
+            s->x11auth->disp = s->x11disp;
+
+            ppl_logevent(("Requesting X11 forwarding"));
+            pktout = ssh_bpp_new_pktout(
+                s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
+            put_stringz(pktout, s->x11auth->protoname);
+            put_stringz(pktout, s->x11auth->datastring);
+            if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
+                put_uint32(pktout, s->x11disp->screennum);
+            pq_push(s->ppl.out_pq, pktout);
+            crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
+            if (pktin->type == SSH1_SMSG_SUCCESS) {
+                ppl_logevent(("X11 forwarding enabled"));
+                s->X11_fwd_enabled = TRUE;
+            } else if (pktin->type == SSH1_SMSG_FAILURE) {
+                ppl_logevent(("X11 forwarding refused"));
+            } else {
+                ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
+                                " in response to X11 forwarding request, "
+                                "type %d (%s)", pktin->type,
+                                ssh1_pkt_type(pktin->type));
+                return;
+            }
+        }
+    }
+
+    portfwdmgr_config(s->portfwdmgr, s->conf);
+    s->portfwdmgr_configured = TRUE;
+
+    if (!conf_get_int(s->conf, CONF_nopty)) {
+	/* Unpick the terminal-speed string. */
+	/* XXX perhaps we should allow no speeds to be sent. */
+	s->ospeed = 38400; s->ispeed = 38400; /* last-resort defaults */
+	sscanf(conf_get_str(s->conf, CONF_termspeed), "%d,%d",
+               &s->ospeed, &s->ispeed);
+	/* Send the pty request. */
+	pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY);
+	put_stringz(pktout, conf_get_str(s->conf, CONF_termtype));
+	put_uint32(pktout, s->term_height);
+	put_uint32(pktout, s->term_width);
+        s->term_width_orig = s->term_width;
+        s->term_height_orig = s->term_height;
+	put_uint32(pktout, 0); /* width in pixels */
+	put_uint32(pktout, 0); /* height in pixels */
+        write_ttymodes_to_packet_from_conf(
+            BinarySink_UPCAST(pktout), s->ppl.frontend, s->conf,
+            1, s->ospeed, s->ispeed);
+        pq_push(s->ppl.out_pq, pktout);
+        crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
+	if (pktin->type == SSH1_SMSG_SUCCESS) {
+            ppl_logevent(("Allocated pty (ospeed %dbps, ispeed %dbps)",
+                          s->ospeed, s->ispeed));
+            s->got_pty = TRUE;
+	} else if (pktin->type == SSH1_SMSG_FAILURE) {
+	    ppl_printf(("Server refused to allocate pty\r\n"));
+	    s->echoedit = TRUE;
+	} else {
+            ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
+                            " in response to pty request, "
+                            "type %d (%s)", pktin->type,
+                            ssh1_pkt_type(pktin->type));
+            crStopV;
+        }
+    } else {
+	s->echoedit = TRUE;
+    }
+
+    /*
+     * Start the shell or command.
+     * 
+     * Special case: if the first-choice command is an SSH-2
+     * subsystem (hence not usable here) and the second choice
+     * exists, we fall straight back to that.
+     */
+    {
+	char *cmd = conf_get_str(s->conf, CONF_remote_cmd);
+	
+	if (conf_get_int(s->conf, CONF_ssh_subsys) &&
+	    conf_get_str(s->conf, CONF_remote_cmd2)) {
+	    cmd = conf_get_str(s->conf, CONF_remote_cmd2);
+	    ssh_got_fallback_cmd(s->ppl.ssh);
+	}
+	if (*cmd) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
+            put_stringz(pktout, cmd);
+        } else {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
+        }
+        pq_push(s->ppl.out_pq, pktout);
+	ppl_logevent(("Started session"));
+    }
+
+    s->session_ready = TRUE;
+
+    /* If an EOF or a window-size change arrived before we were ready
+     * to handle either one, handle them now. */
+    if (s->session_eof_pending) {
+        ssh_ppl_special_cmd(&s->ppl, SS_EOF, 0);
+        s->session_eof_pending = FALSE;
+    }
+    if (s->term_width_orig != s->term_width ||
+        s->term_height_orig != s->term_height)
+	ssh_terminal_size(&s->cl, s->term_width, s->term_height);
+
+    ssh_ldisc_update(s->ppl.ssh);
+    s->finished_setup = TRUE;
+
+    while (1) {
+
+	/*
+	 * By this point, most incoming packets are already being
+	 * handled by filter_queue, and we need only pay attention to
+	 * the unusual ones.
+	 */
+
+	if ((pktin = ssh1_connection_pop(s)) != NULL) {
+            ssh_proto_error(s->ppl.ssh, "Unexpected packet received, "
+                            "type %d (%s)", pktin->type,
+                            ssh1_pkt_type(pktin->type));
+            return;
+	}
+        while (bufchain_size(s->ppl.user_input) > 0) {
+            void *data;
+            int len;
+            bufchain_prefix(s->ppl.user_input, &data, &len);
+            if (len > 512)
+                len = 512;
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
+            put_string(pktout, data, len);
+            pq_push(s->ppl.out_pq, pktout);
+            bufchain_consume(s->ppl.user_input, len);
+        }
+	crReturnV;
+    }
+
+    crFinishV;
+}
+
+static void ssh1_channel_check_close(struct ssh1_channel *c)
+{
+    struct ssh1_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    if (c->halfopen) {
+        /*
+         * 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_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) ||
+         chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE),
+                         (c->closes & CLOSES_RCVD_CLOSE))) &&
+	!(c->closes & CLOSES_SENT_CLOSECONF)) {
+        /*
+         * We have both sent and received CLOSE (or the channel type
+         * doesn't need us to), which means the channel is in final
+         * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we
+         * haven't sent yet.
+         */
+        if (!(c->closes & CLOSES_SENT_CLOSE)) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
+            put_uint32(pktout, c->remoteid);
+            pq_push(s->ppl.out_pq, pktout);
+            c->closes |= CLOSES_SENT_CLOSE;
+        }
+        if (c->closes & CLOSES_RCVD_CLOSE) {
+            pktout = ssh_bpp_new_pktout(
+                s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+            put_uint32(pktout, c->remoteid);
+            pq_push(s->ppl.out_pq, pktout);
+            c->closes |= CLOSES_SENT_CLOSECONF;
+        }
+    }
+
+    if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) {
+        /*
+         * We have both sent and received CLOSE_CONFIRMATION, which
+         * means we're completely done with the channel.
+         */
+        ssh1_channel_destroy(c);
+    }
+}
+
+static void ssh1_channel_try_eof(struct ssh1_channel *c)
+{
+    struct ssh1_connection_state *s = c->connlayer;
+    PktOut *pktout;
+    assert(c->pending_eof);          /* precondition for calling us */
+    if (c->halfopen)
+        return;                 /* can't close: not even opened yet */
+
+    c->pending_eof = FALSE;            /* we're about to send it */
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
+    put_uint32(pktout, c->remoteid);
+    pq_push(s->ppl.out_pq, pktout);
+    c->closes |= CLOSES_SENT_CLOSE;
+
+    ssh1_channel_check_close(c);
+}
+
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel.  This converts the channel into a zombie.
+ */
+static void ssh1_channel_close_local(struct ssh1_channel *c,
+                                     const char *reason)
+{
+    struct ssh1_connection_state *s = c->connlayer;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    const char *msg = chan_log_close_msg(c->chan);
+
+    if (msg != NULL)
+        ppl_logevent(("%s%s%s", msg, reason ? " " : "", reason ? reason : ""));
+
+    chan_free(c->chan);
+    c->chan = zombiechan_new();
+}
+
+static void ssh1_check_termination_callback(void *vctx)
+{
+    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
+    ssh1_check_termination(s);
+}
+
+static void ssh1_channel_destroy(struct ssh1_channel *c)
+{
+    struct ssh1_connection_state *s = c->connlayer;
+
+    ssh1_channel_close_local(c, NULL);
+    del234(s->channels, c);
+    ssh1_channel_free(c);
+
+    /*
+     * If that was the last channel left open, we might need to
+     * terminate. But we'll be a bit cautious, by doing that in a
+     * toplevel callback, just in case anything on the current call
+     * stack objects to this entire PPL being freed.
+     */
+    queue_toplevel_callback(ssh1_check_termination_callback, s);
+}
+
+static int ssh1_check_termination(struct ssh1_connection_state *s)
+{
+    /*
+     * Decide whether we should terminate the SSH connection now.
+     * Called after a channel goes away, or when the main session
+     * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either
+     * is left.
+     */
+    if (s->session_terminated && count234(s->channels) == 0) {
+        PktOut *pktout = ssh_bpp_new_pktout(
+            s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION);
+        pq_push(s->ppl.out_pq, pktout);
+
+        ssh_user_close(s->ppl.ssh, "Session finished");
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/*
+ * Set up most of a new ssh1_channel. Leaves chan untouched (since it
+ * will sometimes have been filled in before calling this).
+ */
+static void ssh1_channel_init(struct ssh1_channel *c)
+{
+    struct ssh1_connection_state *s = c->connlayer;
+    c->closes = 0;
+    c->pending_eof = FALSE;
+    c->throttling_conn = FALSE;
+    c->sc.vt = &ssh1channel_vtable;
+    c->localid = alloc_channel_id(s->channels, struct ssh1_channel);
+    add234(s->channels, c);
+}
+
+static Conf *ssh1channel_get_conf(SshChannel *sc)
+{
+    struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc);
+    struct ssh1_connection_state *s = c->connlayer;
+    return s->conf;
+}
+
+static void ssh1channel_write_eof(SshChannel *sc)
+{
+    struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc);
+
+    if (c->closes & CLOSES_SENT_CLOSE)
+        return;
+
+    c->pending_eof = TRUE;
+    ssh1_channel_try_eof(c);
+}
+
+static void ssh1channel_unclean_close(SshChannel *sc, const char *err)
+{
+    struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc);
+    char *reason;
+
+    reason = dupprintf("due to local error: %s", err);
+    ssh1_channel_close_local(c, reason);
+    sfree(reason);
+    c->pending_eof = FALSE;   /* this will confuse a zombie channel */
+
+    ssh1_channel_check_close(c);
+}
+
+static void ssh1channel_unthrottle(SshChannel *sc, int bufsize)
+{
+    struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc);
+    struct ssh1_connection_state *s = c->connlayer;
+
+    if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) {
+	c->throttling_conn = 0;
+	ssh_throttle_conn(s->ppl.ssh, -1);
+    }
+}
+
+static int ssh1channel_write(SshChannel *sc, const void *buf, int len)
+{
+    struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc);
+    struct ssh1_connection_state *s = c->connlayer;
+
+    assert(!(c->closes & CLOSES_SENT_CLOSE));
+
+    PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA);
+    put_uint32(pktout, c->remoteid);
+    put_string(pktout, buf, len);
+    pq_push(s->ppl.out_pq, pktout);
+
+    /*
+     * 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;
+}
+
+static SshChannel *ssh1_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *org, Channel *chan)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh1_channel *c = snew(struct ssh1_channel);
+    PktOut *pktout;
+
+    c->connlayer = s;
+    ssh1_channel_init(c);
+    c->halfopen = TRUE;
+    c->chan = chan;
+
+    ppl_logevent(("Opening connection to %s:%d for %s", hostname, port, org));
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN);
+    put_uint32(pktout, c->localid);
+    put_stringz(pktout, hostname);
+    put_uint32(pktout, port);
+    /* originator string would go here, but we didn't specify
+     * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */
+    pq_push(s->ppl.out_pq, pktout);
+
+    return &c->sc;
+}
+
+static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
+                                   PktIn *pktin, void *ctx)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+    if (pktin->type == SSH1_SMSG_SUCCESS) {
+	ppl_logevent(("Remote port forwarding from %s enabled",
+                      rpf->log_description));
+    } else {
+	ppl_logevent(("Remote port forwarding from %s refused",
+                      rpf->log_description));
+
+	struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+	assert(realpf == rpf);
+        portfwdmgr_close(s->portfwdmgr, rpf->pfr);
+	free_rportfwd(rpf);
+    }
+}
+
+static struct ssh_rportfwd *ssh1_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+    struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+    rpf->shost = dupstr(shost);
+    rpf->sport = sport;
+    rpf->dhost = dupstr(dhost);
+    rpf->dport = dport;
+    rpf->addressfamily = addressfamily;
+    rpf->log_description = dupstr(log_description);
+    rpf->pfr = pfr;
+
+    if (add234(s->rportfwds, rpf) != rpf) {
+        free_rportfwd(rpf);
+        return NULL;
+    }
+
+    PktOut *pktout = ssh_bpp_new_pktout(
+        s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST);
+    put_uint32(pktout, rpf->sport);
+    put_stringz(pktout, rpf->dhost);
+    put_uint32(pktout, rpf->dport);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf);
+
+    return rpf;
+}
+
+static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+    /*
+     * We cannot cancel listening ports on the server side in SSH-1!
+     * There's no message to support it.
+     */
+}
+
+static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+    return conf_get_int(s->conf, CONF_agentfwd) && agent_exists();
+}
+
+static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    PktOut *pktout;
+
+    if (code == SS_PING || code == SS_NOP) {
+        if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+            put_stringz(pktout, "");
+            pq_push(s->ppl.out_pq, pktout);
+        }
+    } else if (code == SS_EOF) {
+	if (!s->session_ready) {
+	    /*
+	     * Buffer the EOF to send as soon as the main session is
+	     * fully set up.
+	     */
+            s->session_eof_pending = TRUE;
+	} else if (!s->session_eof_sent) {
+	    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
+            pq_push(s->ppl.out_pq, pktout);
+            ppl_logevent(("Sent EOF message"));
+            s->session_eof_sent = TRUE;
+	}
+    }
+}
+
+static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+
+    s->term_width = width;
+    s->term_height = height;
+
+    if (s->session_ready) {
+        PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
+        put_uint32(pktout, s->term_height);
+        put_uint32(pktout, s->term_width);
+        put_uint32(pktout, 0);
+        put_uint32(pktout, 0);
+	pq_push(s->ppl.out_pq, pktout);
+    }
+}
+
+static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+
+    if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+        s->stdout_throttling = 0;
+        ssh_throttle_conn(s->ppl.ssh, -1);
+    }
+}
+
+static int ssh1_stdin_backlog(ConnectionLayer *cl)
+{
+    return 0;
+}
+
+static void ssh1_throttle_all_channels(ConnectionLayer *cl, int throttled)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+    struct ssh1_channel *c;
+    int i;
+
+    for (i = 0; NULL != (c = index234(s->channels, i)); i++)
+        chan_set_input_wanted(c->chan, !throttled);
+}
+
+static int ssh1_ldisc_option(ConnectionLayer *cl, int option)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(cl, struct ssh1_connection_state, cl);
+
+    /* We always return the same value for LD_ECHO and LD_EDIT */
+    return s->echoedit;
+}
+
+static int ssh1_connection_want_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    return s->session_ready && !s->session_eof_sent;
+}
+
+static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+    if (s->session_ready && !s->session_eof_sent)
+        queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh1_connection_state *s =
+        FROMFIELD(ppl, struct ssh1_connection_state, ppl);
+
+    conf_free(s->conf);
+    s->conf = conf_copy(conf);
+
+    if (s->portfwdmgr_configured)
+        portfwdmgr_config(s->portfwdmgr, s->conf);
+}

+ 1206 - 0
source/putty/ssh1login.c

@@ -0,0 +1,1206 @@
+/*
+ * Packet protocol layer for the SSH-1 login phase (combining what
+ * SSH-2 would think of as key exchange and user authentication).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshcr.h"
+
+struct ssh1_login_state {
+    int crState;
+
+    PacketProtocolLayer *successor_layer;
+
+    Conf *conf;
+
+    char *savedhost;
+    int savedport;
+    int try_agent_auth;
+
+    int remote_protoflags;
+    int local_protoflags;
+    unsigned char session_key[32];
+    char *username;
+    agent_pending_query *auth_agent_query;
+
+    int len;
+    unsigned char *rsabuf;
+    unsigned long supported_ciphers_mask, supported_auths_mask;
+    int tried_publickey, tried_agent;
+    int tis_auth_refused, ccard_auth_refused;
+    unsigned char cookie[8];
+    unsigned char session_id[16];
+    int cipher_type;
+    strbuf *publickey_blob;
+    char *publickey_comment;
+    int privatekey_available, privatekey_encrypted;
+    prompts_t *cur_prompt;
+    int userpass_ret;
+    char c;
+    int pwpkt_type;
+    void *agent_response_to_free;
+    ptrlen agent_response;
+    BinarySource asrc[1];          /* response from SSH agent */
+    int keyi, nkeys;
+    int authed;
+    struct RSAKey key;
+    Bignum challenge;
+    ptrlen comment;
+    int dlgret;
+    Filename *keyfile;
+    struct RSAKey servkey, hostkey;
+    int want_user_input;
+
+    PacketProtocolLayer ppl;
+};
+
+static void ssh1_login_free(PacketProtocolLayer *); 
+static void ssh1_login_process_queue(PacketProtocolLayer *);
+static void ssh1_login_dialog_callback(void *, int);
+static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
+                                   SessionSpecialCode code, int arg);
+static int ssh1_login_want_user_input(PacketProtocolLayer *ppl);
+static void ssh1_login_got_user_input(PacketProtocolLayer *ppl);
+static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const struct PacketProtocolLayerVtable ssh1_login_vtable = {
+    ssh1_login_free,
+    ssh1_login_process_queue,
+    ssh1_common_get_specials,
+    ssh1_login_special_cmd,
+    ssh1_login_want_user_input,
+    ssh1_login_got_user_input,
+    ssh1_login_reconfigure,
+    NULL /* no layer names in SSH-1 */,
+};
+
+static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
+static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen);
+
+PacketProtocolLayer *ssh1_login_new(
+    Conf *conf, const char *host, int port,
+    PacketProtocolLayer *successor_layer)
+{
+    struct ssh1_login_state *s = snew(struct ssh1_login_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh1_login_vtable;
+
+    s->conf = conf_copy(conf);
+    s->savedhost = dupstr(host);
+    s->savedport = port;
+    s->successor_layer = successor_layer;
+    return &s->ppl;
+}
+
+static void ssh1_login_free(PacketProtocolLayer *ppl)
+{
+    struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl);
+
+    if (s->successor_layer)
+        ssh_ppl_free(s->successor_layer);
+
+    conf_free(s->conf);
+    sfree(s->savedhost);
+    sfree(s->rsabuf);
+    sfree(s->username);
+    if (s->publickey_blob)
+        strbuf_free(s->publickey_blob);
+    sfree(s->publickey_comment);
+    if (s->cur_prompt)
+        free_prompts(s->cur_prompt);
+    sfree(s->agent_response_to_free);
+    if (s->auth_agent_query)
+        agent_cancel_query(s->auth_agent_query);
+    sfree(s);
+}
+
+int ssh1_common_filter_queue(PacketProtocolLayer *ppl)
+{
+    PktIn *pktin;
+    ptrlen msg;
+
+    while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH1_MSG_DISCONNECT:
+            msg = get_string(pktin);
+            ssh_remote_error(ppl->ssh,
+                             "Server sent disconnect message:\n\"%.*s\"",
+                             PTRLEN_PRINTF(msg));
+            return TRUE;               /* indicate that we've been freed */
+
+          case SSH1_MSG_DEBUG:
+            msg = get_string(pktin);
+            ppl_logevent(("Remote debug message: %.*s", PTRLEN_PRINTF(msg)));
+            pq_pop(ppl->in_pq);
+            break;
+
+          case SSH1_MSG_IGNORE:
+            /* Do nothing, because we're ignoring it! Duhh. */
+            pq_pop(ppl->in_pq);
+            break;
+
+          default:
+            return FALSE;
+        }
+    }
+
+    return FALSE;
+}
+
+static int ssh1_login_filter_queue(struct ssh1_login_state *s)
+{
+    return ssh1_common_filter_queue(&s->ppl);
+}
+
+static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
+{
+    if (ssh1_login_filter_queue(s))
+        return NULL;
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl);
+    PktIn *pktin;
+    PktOut *pkt;
+    int i;
+
+    /* Filter centrally handled messages off the front of the queue on
+     * every entry to this coroutine, no matter where we're resuming
+     * from, even if we're _not_ looping on pq_pop. That way we can
+     * still proactively handle those messages even if we're waiting
+     * for a user response. */
+    if (ssh1_login_filter_queue(s))
+        return;
+
+    crBegin(s->crState);
+
+    crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+    if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
+        ssh_proto_error(s->ppl.ssh, "Public key packet not received");
+        return;
+    }
+
+    ppl_logevent(("Received public keys"));
+
+    {
+        ptrlen pl = get_data(pktin, 8);
+        memcpy(s->cookie, pl.ptr, pl.len);
+    }
+
+    get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST);
+    get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST);
+
+    s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */
+
+    /*
+     * Log the host key fingerprint.
+     */
+    if (!get_err(pktin)) {
+        char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
+        ppl_logevent(("Host key fingerprint is:"));
+        ppl_logevent(("      %s", fingerprint));
+        sfree(fingerprint);
+    }
+
+    s->remote_protoflags = get_uint32(pktin);
+    s->supported_ciphers_mask = get_uint32(pktin);
+    s->supported_auths_mask = get_uint32(pktin);
+
+    if (get_err(pktin)) {
+        ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet");
+        return;
+    }
+
+    if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA))
+        s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
+
+    s->local_protoflags =
+        s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
+    s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
+
+    {
+        struct MD5Context md5c;
+
+        MD5Init(&md5c);
+        for (i = (bignum_bitcount(s->hostkey.modulus) + 7) / 8; i-- ;)
+            put_byte(&md5c, bignum_byte(s->hostkey.modulus, i));
+        for (i = (bignum_bitcount(s->servkey.modulus) + 7) / 8; i-- ;)
+            put_byte(&md5c, bignum_byte(s->servkey.modulus, i));
+        put_data(&md5c, s->cookie, 8);
+        MD5Final(s->session_id, &md5c);
+    }
+
+    for (i = 0; i < 32; i++)
+        s->session_key[i] = random_byte();
+
+    /*
+     * Verify that the `bits' and `bytes' parameters match.
+     */
+    if (s->hostkey.bits > s->hostkey.bytes * 8 ||
+        s->servkey.bits > s->servkey.bytes * 8) {
+        ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted");
+        return;
+    }
+
+    s->len = (s->hostkey.bytes > s->servkey.bytes ?
+              s->hostkey.bytes : s->servkey.bytes);
+
+    s->rsabuf = snewn(s->len, unsigned char);
+
+    /*
+     * Verify the host key.
+     */
+    {
+        /*
+         * First format the key into a string.
+         */
+        int len = rsastr_len(&s->hostkey);
+        char *fingerprint;
+        char *keystr = snewn(len, char);
+        rsastr_fmt(keystr, &s->hostkey);
+        fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
+
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL);
+        sfree(fingerprint);
+        if (s->dlgret == 0) {          /* did not match */
+            sfree(keystr);
+            ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually "
+                            "configured list");
+            return;
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            s->dlgret = verify_ssh_host_key(
+                s->ppl.frontend, s->savedhost, s->savedport,
+                "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s);
+            sfree(keystr);
+#ifdef FUZZING
+            s->dlgret = 1;
+#endif
+            crMaybeWaitUntilV(s->dlgret >= 0);
+
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh,
+                               "User aborted at host key verification");
+                return;
+            }
+        } else {
+            sfree(keystr);
+        }
+    }
+
+    for (i = 0; i < 32; i++) {
+        s->rsabuf[i] = s->session_key[i];
+        if (i < 16)
+            s->rsabuf[i] ^= s->session_id[i];
+    }
+
+    {
+        struct RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ?
+                                  &s->servkey : &s->hostkey);
+        struct RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ?
+                                 &s->hostkey : &s->servkey);
+
+        if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) ||
+            !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) {
+            ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed "
+                            "due to bad formatting");
+            return;
+        }
+    }
+
+    ppl_logevent(("Encrypted session key"));
+
+    {
+        int cipher_chosen = 0, warn = 0;
+        const char *cipher_string = NULL;
+        int i;
+	for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+	    int next_cipher = conf_get_int_int(
+                s->conf, CONF_ssh_cipherlist, i);
+            if (next_cipher == CIPHER_WARN) {
+                /* If/when we choose a cipher, warn about it */
+                warn = 1;
+            } else if (next_cipher == CIPHER_AES) {
+                /* XXX Probably don't need to mention this. */
+                ppl_logevent(("AES not supported in SSH-1, skipping"));
+            } else {
+                switch (next_cipher) {
+                  case CIPHER_3DES:     s->cipher_type = SSH_CIPHER_3DES;
+                    cipher_string = "3DES"; break;
+                  case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
+                    cipher_string = "Blowfish"; break;
+                  case CIPHER_DES:      s->cipher_type = SSH_CIPHER_DES;
+                    cipher_string = "single-DES"; break;
+                }
+                if (s->supported_ciphers_mask & (1 << s->cipher_type))
+                    cipher_chosen = 1;
+            }
+        }
+        if (!cipher_chosen) {
+            if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) {
+                ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol "
+                                "by not supporting 3DES encryption");
+            } else {
+                /* shouldn't happen */
+                ssh_sw_abort(s->ppl.ssh, "No supported ciphers found");
+            }
+            return;
+        }
+
+        /* Warn about chosen cipher if necessary. */
+        if (warn) {
+            s->dlgret = askalg(s->ppl.frontend, "cipher", cipher_string,
+                               ssh1_login_dialog_callback, s);
+            crMaybeWaitUntilV(s->dlgret >= 0);
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
+                return;
+            }
+        }
+    }
+
+    switch (s->cipher_type) {
+      case SSH_CIPHER_3DES:
+        ppl_logevent(("Using 3DES encryption"));
+        break;
+      case SSH_CIPHER_DES:
+        ppl_logevent(("Using single-DES encryption"));
+        break;
+      case SSH_CIPHER_BLOWFISH:
+        ppl_logevent(("Using Blowfish encryption"));
+        break;
+    }
+
+    pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY);
+    put_byte(pkt, s->cipher_type);
+    put_data(pkt, s->cookie, 8);
+    put_uint16(pkt, s->len * 8);
+    put_data(pkt, s->rsabuf, s->len);
+    put_uint32(pkt, s->local_protoflags);
+    pq_push(s->ppl.out_pq, pkt);
+
+    ppl_logevent(("Trying to enable encryption..."));
+
+    sfree(s->rsabuf);
+    s->rsabuf = NULL;
+
+    /*
+     * Force the BPP to synchronously marshal all packets up to and
+     * including the SESSION_KEY into wire format, before we turn on
+     * crypto.
+     */
+    ssh_bpp_handle_output(s->ppl.bpp);
+
+    {
+        const struct ssh1_cipheralg *cipher =
+            (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish :
+             s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des);
+        ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
+        ppl_logevent(("Initialised %s encryption", cipher->text_name));
+    }
+
+    if (s->servkey.modulus) {
+        sfree(s->servkey.modulus);
+        s->servkey.modulus = NULL;
+    }
+    if (s->servkey.exponent) {
+        sfree(s->servkey.exponent);
+        s->servkey.exponent = NULL;
+    }
+    if (s->hostkey.modulus) {
+        sfree(s->hostkey.modulus);
+        s->hostkey.modulus = NULL;
+    }
+    if (s->hostkey.exponent) {
+        sfree(s->hostkey.exponent);
+        s->hostkey.exponent = NULL;
+    }
+    crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+    if (pktin->type != SSH1_SMSG_SUCCESS) {
+        ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled");
+        return;
+    }
+
+    ppl_logevent(("Successfully started encryption"));
+
+    if ((s->username = get_remote_username(s->conf)) == NULL) {
+        s->cur_prompt = new_prompts(s->ppl.frontend);
+        s->cur_prompt->to_server = TRUE;
+        s->cur_prompt->name = dupstr("SSH login name");
+        add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
+        s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+        while (1) {
+            while (s->userpass_ret < 0 &&
+                   bufchain_size(s->ppl.user_input) > 0)
+                s->userpass_ret = get_userpass_input(
+                    s->cur_prompt, s->ppl.user_input);
+
+            if (s->userpass_ret >= 0)
+                break;
+
+            s->want_user_input = TRUE;
+            crReturnV;
+            s->want_user_input = FALSE;
+        }
+        if (!s->userpass_ret) {
+            /*
+             * Failed to get a username. Terminate.
+             */
+            ssh_user_close(s->ppl.ssh, "No username provided");
+            return;
+        }
+        s->username = dupstr(s->cur_prompt->prompts[0]->result);
+        free_prompts(s->cur_prompt);
+        s->cur_prompt = NULL;
+    }
+
+    pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER);
+    put_stringz(pkt, s->username);
+    pq_push(s->ppl.out_pq, pkt);
+
+    ppl_logevent(("Sent username \"%s\"", s->username));
+    if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
+        ppl_printf(("Sent username \"%s\"\r\n", s->username));
+
+    crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+    if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) {
+        /* We must not attempt PK auth. Pretend we've already tried it. */
+        s->tried_publickey = s->tried_agent = TRUE;
+    } else {
+        s->tried_publickey = s->tried_agent = FALSE;
+    }
+    s->tis_auth_refused = s->ccard_auth_refused = FALSE;
+
+    /*
+     * Load the public half of any configured keyfile for later use.
+     */
+    s->keyfile = conf_get_filename(s->conf, CONF_keyfile);
+    if (!filename_is_null(s->keyfile)) {
+        int keytype;
+        ppl_logevent(("Reading key file \"%.150s\"",
+                      filename_to_str(s->keyfile)));
+        keytype = key_type(s->keyfile);
+        if (keytype == SSH_KEYTYPE_SSH1 ||
+            keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+            const char *error;
+            s->publickey_blob = strbuf_new();
+            if (rsa_ssh1_loadpub(s->keyfile,
+                                 BinarySink_UPCAST(s->publickey_blob),
+                                 &s->publickey_comment, &error)) {
+                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
+                if (!s->privatekey_available)
+                    ppl_logevent(("Key file contains public key only"));
+                s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL);
+            } else {
+                ppl_logevent(("Unable to load key (%s)", error));
+                ppl_printf(("Unable to load key file \"%s\" (%s)\r\n",
+                            filename_to_str(s->keyfile), error));
+
+                strbuf_free(s->publickey_blob);
+                s->publickey_blob = NULL;
+            }
+        } else {
+            ppl_logevent(("Unable to use this key file (%s)",
+                          key_type_to_str(keytype)));
+            ppl_printf(("Unable to use key file \"%s\" (%s)\r\n",
+                        filename_to_str(s->keyfile),
+                        key_type_to_str(keytype)));
+        }
+    }
+
+    /* Check whether we're configured to try Pageant, and also whether
+     * it's available. */
+    s->try_agent_auth = (conf_get_int(s->conf, CONF_tryagent) &&
+                         agent_exists());
+
+    while (pktin->type == SSH1_SMSG_FAILURE) {
+        s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+
+        if (s->try_agent_auth && !s->tried_agent) {
+            /*
+             * Attempt RSA authentication using Pageant.
+             */
+            s->authed = FALSE;
+            s->tried_agent = 1;
+            ppl_logevent(("Pageant is running. Requesting keys."));
+
+            /* Request the keys held by the agent. */
+            {
+                strbuf *request = strbuf_new_for_agent_query();
+                put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES);
+                ssh1_login_agent_query(s, request);
+                strbuf_free(request);
+                crMaybeWaitUntilV(!s->auth_agent_query);
+            }
+            BinarySource_BARE_INIT(
+                s->asrc, s->agent_response.ptr, s->agent_response.len);
+
+            get_uint32(s->asrc); /* skip length field */
+            if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+                s->nkeys = toint(get_uint32(s->asrc));
+                if (s->nkeys < 0) {
+                    ppl_logevent(("Pageant reported negative key count %d",
+                                  s->nkeys));
+                    s->nkeys = 0;
+                }
+                ppl_logevent(("Pageant has %d SSH-1 keys", s->nkeys));
+                for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+                    size_t start, end;
+                    start = s->asrc->pos;
+                    get_rsa_ssh1_pub(s->asrc, &s->key,
+                                     RSA_SSH1_EXPONENT_FIRST);
+                    end = s->asrc->pos;
+                    s->comment = get_string(s->asrc);
+                    if (get_err(s->asrc)) {
+                        ppl_logevent(("Pageant key list packet was truncated"));
+                        break;
+                    }
+                    if (s->publickey_blob) {
+                        ptrlen keystr = make_ptrlen(
+                            (const char *)s->asrc->data + start, end - start);
+
+                        if (keystr.len == s->publickey_blob->len &&
+                            !memcmp(keystr.ptr, s->publickey_blob->s,
+                                    s->publickey_blob->len)) {
+                            ppl_logevent(("Pageant key #%d matches "
+                                          "configured key file", s->keyi));
+                            s->tried_publickey = 1;
+                        } else
+                            /* Skip non-configured key */
+                            continue;
+                    }
+                    ppl_logevent(("Trying Pageant key #%d", s->keyi));
+                    pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
+                    put_mp_ssh1(pkt, s->key.modulus);
+                    pq_push(s->ppl.out_pq, pkt);
+                    crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+                                      != NULL);
+                    if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                        ppl_logevent(("Key refused"));
+                        continue;
+                    }
+                    ppl_logevent(("Received RSA challenge"));
+                    s->challenge = get_mp_ssh1(pktin);
+                    if (get_err(pktin)) {
+                        freebn(s->challenge);
+                        ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
+                                        "was badly formatted");
+                        return;
+                    }
+
+                    {
+                        strbuf *agentreq;
+                        const char *ret;
+
+                        agentreq = strbuf_new_for_agent_query();
+                        put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
+                        put_uint32(agentreq, bignum_bitcount(s->key.modulus));
+                        put_mp_ssh1(agentreq, s->key.exponent);
+                        put_mp_ssh1(agentreq, s->key.modulus);
+                        put_mp_ssh1(agentreq, s->challenge);
+                        put_data(agentreq, s->session_id, 16);
+                        put_uint32(agentreq, 1);    /* response format */
+                        ssh1_login_agent_query(s, agentreq);
+                        strbuf_free(agentreq);
+                        crMaybeWaitUntilV(!s->auth_agent_query);
+
+                        ret = s->agent_response.ptr;
+                        if (ret) {
+                            if (s->agent_response.len >= 5+16 &&
+                                ret[4] == SSH1_AGENT_RSA_RESPONSE) {
+                                ppl_logevent(("Sending Pageant's response"));
+                                pkt = ssh_bpp_new_pktout(
+                                    s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
+                                put_data(pkt, ret + 5, 16);
+                                pq_push(s->ppl.out_pq, pkt);
+                                sfree((char *)ret);
+                                crMaybeWaitUntilV(
+                                    (pktin = ssh1_login_pop(s))
+                                    != NULL);
+                                if (pktin->type == SSH1_SMSG_SUCCESS) {
+                                    ppl_logevent(("Pageant's response "
+                                                  "accepted"));
+                                    if (flags & FLAG_VERBOSE) {
+                                        ppl_printf(("Authenticated using RSA "
+                                                    "key \"%.*s\" from "
+                                                    "agent\r\n", PTRLEN_PRINTF(
+                                                        s->comment)));
+                                    }
+                                    s->authed = TRUE;
+                                } else
+                                    ppl_logevent(("Pageant's response not "
+                                                  "accepted"));
+                            } else {
+                                ppl_logevent(("Pageant failed to answer "
+                                              "challenge"));
+                                sfree((char *)ret);
+                            }
+                        } else {
+                            ppl_logevent(("No reply received from Pageant"));
+                        }
+                    }
+                    freebn(s->key.exponent);
+                    freebn(s->key.modulus);
+                    freebn(s->challenge);
+                    if (s->authed)
+                        break;
+                }
+                sfree(s->agent_response_to_free);
+                s->agent_response_to_free = NULL;
+                if (s->publickey_blob && !s->tried_publickey)
+                    ppl_logevent(("Configured key file not in Pageant"));
+            } else {
+                ppl_logevent(("Failed to get reply from Pageant"));
+            }
+            if (s->authed)
+                break;
+        }
+        if (s->publickey_blob && s->privatekey_available &&
+            !s->tried_publickey) {
+            /*
+             * Try public key authentication with the specified
+             * key file.
+             */
+            int got_passphrase; /* need not be kept over crReturn */
+            if (flags & FLAG_VERBOSE)
+                ppl_printf(("Trying public key authentication.\r\n"));
+            ppl_logevent(("Trying public key \"%s\"",
+                          filename_to_str(s->keyfile)));
+            s->tried_publickey = 1;
+            got_passphrase = FALSE;
+            while (!got_passphrase) {
+                /*
+                 * Get a passphrase, if necessary.
+                 */
+                int retd;
+                char *passphrase = NULL;    /* only written after crReturn */
+                const char *error;
+                if (!s->privatekey_encrypted) {
+                    if (flags & FLAG_VERBOSE)
+                        ppl_printf(("No passphrase required.\r\n"));
+                    passphrase = NULL;
+                } else {
+                    s->cur_prompt = new_prompts(s->ppl.frontend);
+                    s->cur_prompt->to_server = FALSE;
+                    s->cur_prompt->name = dupstr("SSH key passphrase");
+                    add_prompt(s->cur_prompt,
+                               dupprintf("Passphrase for key \"%.100s\": ",
+                                         s->publickey_comment), FALSE);
+                    s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+                    while (1) {
+                        while (s->userpass_ret < 0 &&
+                               bufchain_size(s->ppl.user_input) > 0)
+                            s->userpass_ret = get_userpass_input(
+                                s->cur_prompt, s->ppl.user_input);
+
+                        if (s->userpass_ret >= 0)
+                            break;
+
+                        s->want_user_input = TRUE;
+                        crReturnV;
+                        s->want_user_input = FALSE;
+                    }
+                    if (!s->userpass_ret) {
+                        /* Failed to get a passphrase. Terminate. */
+                        ssh_sw_abort(s->ppl.ssh, "Unable to authenticate");
+                        return;
+                    }
+                    passphrase = dupstr(s->cur_prompt->prompts[0]->result);
+                    free_prompts(s->cur_prompt);
+                    s->cur_prompt = NULL;
+                }
+                /*
+                 * Try decrypting key with passphrase.
+                 */
+                retd = rsa_ssh1_loadkey(
+                    s->keyfile, &s->key, passphrase, &error);
+                if (passphrase) {
+                    smemclr(passphrase, strlen(passphrase));
+                    sfree(passphrase);
+                }
+                if (retd == 1) {
+                    /* Correct passphrase. */
+                    got_passphrase = TRUE;
+                } else if (retd == 0) {
+                    ppl_printf(("Couldn't load private key from %s (%s).\r\n",
+                                filename_to_str(s->keyfile), error));
+                    got_passphrase = FALSE;
+                    break;             /* go and try something else */
+                } else if (retd == -1) {
+                    ppl_printf(("Wrong passphrase.\r\n"));
+                    got_passphrase = FALSE;
+                    /* and try again */
+                } else {
+                    assert(0 && "unexpected return from rsa_ssh1_loadkey()");
+                    got_passphrase = FALSE;   /* placate optimisers */
+                }
+            }
+
+            if (got_passphrase) {
+
+                /*
+                 * Send a public key attempt.
+                 */
+                pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
+                put_mp_ssh1(pkt, s->key.modulus);
+                pq_push(s->ppl.out_pq, pkt);
+
+                crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+                                  != NULL);
+                if (pktin->type == SSH1_SMSG_FAILURE) {
+                    ppl_printf(("Server refused our public key.\r\n"));
+                    continue;          /* go and try something else */
+                }
+                if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                    ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                                    " in response to offer of public key, "
+                                    "type %d (%s)", pktin->type,
+                                    ssh1_pkt_type(pktin->type));
+                    return;
+                }
+
+                {
+                    int i;
+                    unsigned char buffer[32];
+                    Bignum challenge, response;
+
+                    challenge = get_mp_ssh1(pktin);
+                    if (get_err(pktin)) {
+                        freebn(challenge);
+                        ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
+                                        "was badly formatted");
+                        return;
+                    }
+                    response = rsa_ssh1_decrypt(challenge, &s->key);
+                    freebn(s->key.private_exponent);/* burn the evidence */
+
+                    for (i = 0; i < 32; i++) {
+                        buffer[i] = bignum_byte(response, 31 - i);
+                    }
+
+                    {
+                        struct MD5Context md5c;
+                        MD5Init(&md5c);
+                        put_data(&md5c, buffer, 32);
+                        put_data(&md5c, s->session_id, 16);
+                        MD5Final(buffer, &md5c);
+                    }
+
+                    pkt = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
+                    put_data(pkt, buffer, 16);
+                    pq_push(s->ppl.out_pq, pkt);
+
+                    freebn(challenge);
+                    freebn(response);
+                }
+
+                crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+                                  != NULL);
+                if (pktin->type == SSH1_SMSG_FAILURE) {
+                    if (flags & FLAG_VERBOSE)
+                        ppl_printf(("Failed to authenticate with"
+                                    " our public key.\r\n"));
+                    continue;          /* go and try something else */
+                } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+                    ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                                    " in response to RSA authentication, "
+                                    "type %d (%s)", pktin->type,
+                                    ssh1_pkt_type(pktin->type));
+                    return;
+                }
+
+                break;                 /* we're through! */
+            }
+
+        }
+
+        /*
+         * Otherwise, try various forms of password-like authentication.
+         */
+        s->cur_prompt = new_prompts(s->ppl.frontend);
+
+        if (conf_get_int(s->conf, CONF_try_tis_auth) &&
+            (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
+            !s->tis_auth_refused) {
+            s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+            ppl_logevent(("Requested TIS authentication"));
+            pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
+            pq_push(s->ppl.out_pq, pkt);
+            crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+            if (pktin->type == SSH1_SMSG_FAILURE) {
+                ppl_logevent(("TIS authentication declined"));
+                if (flags & FLAG_INTERACTIVE)
+                    ppl_printf(("TIS authentication refused.\r\n"));
+                s->tis_auth_refused = 1;
+                continue;
+            } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+                ptrlen challenge;
+                char *instr_suf, *prompt;
+
+                challenge = get_string(pktin);
+                if (get_err(pktin)) {
+                    ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
+                                    "badly formed");
+                    return;
+                }
+                ppl_logevent(("Received TIS challenge"));
+                s->cur_prompt->to_server = TRUE;
+                s->cur_prompt->name = dupstr("SSH TIS authentication");
+                /* Prompt heuristic comes from OpenSSH */
+                if (memchr(challenge.ptr, '\n', challenge.len)) {
+                    instr_suf = dupstr("");
+                    prompt = mkstr(challenge);
+                } else {
+                    instr_suf = mkstr(challenge);
+                    prompt = dupstr("Response: ");
+                }
+                s->cur_prompt->instruction =
+                    dupprintf("Using TIS authentication.%s%s",
+                              (*instr_suf) ? "\n" : "",
+                              instr_suf);
+                s->cur_prompt->instr_reqd = TRUE;
+                add_prompt(s->cur_prompt, prompt, FALSE);
+                sfree(instr_suf);
+            } else {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                                " in response to TIS authentication, "
+                                "type %d (%s)", pktin->type,
+                                ssh1_pkt_type(pktin->type));
+                return;
+            }
+        }
+        if (conf_get_int(s->conf, CONF_try_tis_auth) &&
+            (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
+            !s->ccard_auth_refused) {
+            s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+            ppl_logevent(("Requested CryptoCard authentication"));
+            pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
+            pq_push(s->ppl.out_pq, pkt);
+            crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+            if (pktin->type == SSH1_SMSG_FAILURE) {
+                ppl_logevent(("CryptoCard authentication declined"));
+                ppl_printf(("CryptoCard authentication refused.\r\n"));
+                s->ccard_auth_refused = 1;
+                continue;
+            } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+                ptrlen challenge;
+                char *instr_suf, *prompt;
+
+                challenge = get_string(pktin);
+                if (get_err(pktin)) {
+                    ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
+                                    "was badly formed");
+                    return;
+                }
+                ppl_logevent(("Received CryptoCard challenge"));
+                s->cur_prompt->to_server = TRUE;
+                s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
+                s->cur_prompt->name_reqd = FALSE;
+                /* Prompt heuristic comes from OpenSSH */
+                if (memchr(challenge.ptr, '\n', challenge.len)) {
+                    instr_suf = dupstr("");
+                    prompt = mkstr(challenge);
+                } else {
+                    instr_suf = mkstr(challenge);
+                    prompt = dupstr("Response: ");
+                }
+                s->cur_prompt->instruction =
+                    dupprintf("Using CryptoCard authentication.%s%s",
+                              (*instr_suf) ? "\n" : "",
+                              instr_suf);
+                s->cur_prompt->instr_reqd = TRUE;
+                add_prompt(s->cur_prompt, prompt, FALSE);
+                sfree(instr_suf);
+            } else {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                                " in response to TIS authentication, "
+                                "type %d (%s)", pktin->type,
+                                ssh1_pkt_type(pktin->type));
+                return;
+            }
+        }
+        if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+            if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
+                ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
+                             "available");
+                return;
+            }
+            s->cur_prompt->to_server = TRUE;
+            s->cur_prompt->name = dupstr("SSH password");
+            add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+                                                s->username, s->savedhost),
+                       FALSE);
+        }
+
+        /*
+         * Show password prompt, having first obtained it via a TIS
+         * or CryptoCard exchange if we're doing TIS or CryptoCard
+         * authentication.
+         */
+        s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+        while (1) {
+            while (s->userpass_ret < 0 &&
+                   bufchain_size(s->ppl.user_input) > 0)
+                s->userpass_ret = get_userpass_input(
+                    s->cur_prompt, s->ppl.user_input);
+
+            if (s->userpass_ret >= 0)
+                break;
+
+            s->want_user_input = TRUE;
+            crReturnV;
+            s->want_user_input = FALSE;
+        }
+        if (!s->userpass_ret) {
+            /*
+             * Failed to get a password (for example
+             * because one was supplied on the command line
+             * which has already failed to work). Terminate.
+             */
+            ssh_sw_abort(s->ppl.ssh, "Unable to authenticate");
+            return;
+        }
+
+        if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+            /*
+             * Defence against traffic analysis: we send a
+             * whole bunch of packets containing strings of
+             * different lengths. One of these strings is the
+             * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+             * The others are all random data in
+             * SSH1_MSG_IGNORE packets. This way a passive
+             * listener can't tell which is the password, and
+             * hence can't deduce the password length.
+             * 
+             * Anybody with a password length greater than 16
+             * bytes is going to have enough entropy in their
+             * password that a listener won't find it _that_
+             * much help to know how long it is. So what we'll
+             * do is:
+             * 
+             *  - if password length < 16, we send 15 packets
+             *    containing string lengths 1 through 15
+             * 
+             *  - otherwise, we let N be the nearest multiple
+             *    of 8 below the password length, and send 8
+             *    packets containing string lengths N through
+             *    N+7. This won't obscure the order of
+             *    magnitude of the password length, but it will
+             *    introduce a bit of extra uncertainty.
+             * 
+             * A few servers can't deal with SSH1_MSG_IGNORE, at
+             * least in this context. For these servers, we need
+             * an alternative defence. We make use of the fact
+             * that the password is interpreted as a C string:
+             * so we can append a NUL, then some random data.
+             * 
+             * A few servers can deal with neither SSH1_MSG_IGNORE
+             * here _nor_ a padded password string.
+             * For these servers we are left with no defences
+             * against password length sniffing.
+             */
+            if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
+                !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+                /*
+                 * The server can deal with SSH1_MSG_IGNORE, so
+                 * we can use the primary defence.
+                 */
+                int bottom, top, pwlen, i;
+
+                pwlen = strlen(s->cur_prompt->prompts[0]->result);
+                if (pwlen < 16) {
+                    bottom = 0;    /* zero length passwords are OK! :-) */
+                    top = 15;
+                } else {
+                    bottom = pwlen & ~7;
+                    top = bottom + 7;
+                }
+
+                assert(pwlen >= bottom && pwlen <= top);
+
+                for (i = bottom; i <= top; i++) {
+                    if (i == pwlen) {
+                        pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+                        put_stringz(pkt, s->cur_prompt->prompts[0]->result);
+                        pq_push(s->ppl.out_pq, pkt);
+                    } else {
+                        int j;
+                        strbuf *random_data = strbuf_new();
+                        for (j = 0; j < i; j++)
+                            put_byte(random_data, random_byte());
+
+                        pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+                        put_stringsb(pkt, random_data);
+                        pq_push(s->ppl.out_pq, pkt);
+                    }
+                }
+                ppl_logevent(("Sending password with camouflage packets"));
+            } 
+            else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+                /*
+                 * The server can't deal with SSH1_MSG_IGNORE
+                 * but can deal with padded passwords, so we
+                 * can use the secondary defence.
+                 */
+                strbuf *padded_pw = strbuf_new();
+
+                ppl_logevent(("Sending length-padded password"));
+                pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+                put_asciz(padded_pw, s->cur_prompt->prompts[0]->result);
+                do {
+                    put_byte(padded_pw, random_byte());
+                } while (padded_pw->len % 64 != 0);
+                put_stringsb(pkt, padded_pw);
+                pq_push(s->ppl.out_pq, pkt);
+            } else {
+                /*
+                 * The server is believed unable to cope with
+                 * any of our password camouflage methods.
+                 */
+                ppl_logevent(("Sending unpadded password"));
+                pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+                put_stringz(pkt, s->cur_prompt->prompts[0]->result);
+                pq_push(s->ppl.out_pq, pkt);
+            }
+        } else {
+            pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+            put_stringz(pkt, s->cur_prompt->prompts[0]->result);
+            pq_push(s->ppl.out_pq, pkt);
+        }
+        ppl_logevent(("Sent password"));
+        free_prompts(s->cur_prompt);
+        s->cur_prompt = NULL;
+        crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+        if (pktin->type == SSH1_SMSG_FAILURE) {
+            if (flags & FLAG_VERBOSE)
+                ppl_printf(("Access denied\r\n"));
+            ppl_logevent(("Authentication refused"));
+        } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                            " in response to password authentication, type %d "
+                            "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
+            return;
+        }
+    }
+
+    ppl_logevent(("Authentication successful"));
+
+    if (conf_get_int(s->conf, CONF_compression)) {
+        ppl_logevent(("Requesting compression"));
+        pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION);
+        put_uint32(pkt, 6);         /* gzip compression level */
+        pq_push(s->ppl.out_pq, pkt);
+        crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+	if (pktin->type == SSH1_SMSG_SUCCESS) {
+            /*
+             * We don't have to actually do anything here: the SSH-1
+             * BPP will take care of automatically starting the
+             * compression, by recognising our outgoing request packet
+             * and the success response. (Horrible, but it's the
+             * easiest way to avoid race conditions if other packets
+             * cross in transit.)
+             */
+            ppl_logevent(("Started zlib (RFC1950) compression"));
+	} else if (pktin->type == SSH1_SMSG_FAILURE) {
+            ppl_logevent(("Server refused to enable compression"));
+	    ppl_printf(("Server refused to compress\r\n"));
+        } else {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+                            " in response to compression request, type %d "
+                            "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
+            return;
+	}
+    }
+
+    ssh1_connection_set_local_protoflags(
+        s->successor_layer, s->local_protoflags);
+    {
+        PacketProtocolLayer *successor = s->successor_layer;
+        s->successor_layer = NULL;     /* avoid freeing it ourself */
+        ssh_ppl_replace(&s->ppl, successor);
+        return;   /* we've just freed s, so avoid even touching s->crState */
+    }
+
+    crFinishV;
+}
+
+static void ssh1_login_dialog_callback(void *loginv, int ret)
+{
+    struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
+    s->dlgret = ret;
+    ssh_ppl_process_queue(&s->ppl);
+}
+
+static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req)
+{
+    void *response;
+    int response_len;
+
+    sfree(s->agent_response_to_free);
+    s->agent_response_to_free = NULL;
+
+    s->auth_agent_query = agent_query(req, &response, &response_len,
+                                      ssh1_login_agent_callback, s);
+    if (!s->auth_agent_query)
+        ssh1_login_agent_callback(s, response, response_len);
+}
+
+static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen)
+{
+    struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
+
+    s->auth_agent_query = NULL;
+    s->agent_response_to_free = reply;
+    s->agent_response = make_ptrlen(reply, replylen);
+
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
+                                   SessionSpecialCode code, int arg)
+{
+    struct ssh1_login_state *s =
+        FROMFIELD(ppl, struct ssh1_login_state, ppl);
+    PktOut *pktout;
+
+    if (code == SS_PING || code == SS_NOP) {
+        if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+            put_stringz(pktout, "");
+            pq_push(s->ppl.out_pq, pktout);
+        }
+    }
+}
+
+static int ssh1_login_want_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh1_login_state *s =
+        FROMFIELD(ppl, struct ssh1_login_state, ppl);
+    return s->want_user_input;
+}
+
+static void ssh1_login_got_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh1_login_state *s =
+        FROMFIELD(ppl, struct ssh1_login_state, ppl);
+    if (s->want_user_input)
+        queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh1_login_state *s =
+        FROMFIELD(ppl, struct ssh1_login_state, ppl);
+    ssh_ppl_reconfigure(s->successor_layer, conf);
+}

+ 14 - 9
source/putty/ssh2bpp-bare.c

@@ -53,8 +53,11 @@ static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
 
 #define BPP_READ(ptr, len) do                                   \
     {                                                           \
-        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+        crMaybeWaitUntilV(s->bpp.input_eof ||                   \
+                          bufchain_try_fetch_consume(           \
                               s->bpp.in_raw, ptr, len));        \
+        if (s->bpp.input_eof)                                   \
+            goto eof;                                           \
     } while (0)
 
 static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
@@ -73,7 +76,7 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
         }
 
         if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
-            s->bpp.error = dupstr("Invalid packet length received");
+            ssh_sw_abort(s->bpp.ssh, "Invalid packet length received");
             crStopV;
         }
 
@@ -124,15 +127,17 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
         }
 
         pq_push(&s->bpp.in_pq, s->pktin);
+        s->pktin = NULL;
+    }
 
-        {
-            int type = s->pktin->type;
-            s->pktin = NULL;
-
-            if (type == SSH2_MSG_DISCONNECT)
-                s->bpp.seen_disconnect = TRUE;
-        }
+  eof:
+    if (!s->bpp.expect_close) {
+        ssh_remote_error(s->bpp.ssh,
+                         "Server unexpectedly closed network connection");
+    } else {
+        ssh_remote_eof(s->bpp.ssh, "Server closed network connection");
     }
+
     crFinishV;
 }
 

+ 23 - 13
source/putty/ssh2bpp.c

@@ -170,8 +170,11 @@ void ssh2_bpp_new_incoming_crypto(
 
 #define BPP_READ(ptr, len) do                                   \
     {                                                           \
-        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+        crMaybeWaitUntilV(s->bpp.input_eof ||                   \
+                          bufchain_try_fetch_consume(           \
                               s->bpp.in_raw, ptr, len));        \
+        if (s->bpp.input_eof)                                   \
+            goto eof;                                           \
     } while (0)
 
 static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
@@ -248,8 +251,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
                      s->packetlen-4))
                     break;
                 if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
-                    s->bpp.error = dupprintf(
-                        "No valid incoming packet found");
+                    ssh_sw_abort(s->bpp.ssh,
+                                 "No valid incoming packet found");
                     crStopV;
                 }
             }
@@ -295,8 +298,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
                 s->len % s->cipherblk != 0) {
-                s->bpp.error = dupprintf(
-                    "Incoming packet length field was garbled");
+                ssh_sw_abort(s->bpp.ssh,
+                             "Incoming packet length field was garbled");
                 crStopV;
             }
 
@@ -325,7 +328,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             if (s->in.mac && !ssh2_mac_verify(
                     s->in.mac, s->data, s->len + 4, s->in.sequence)) {
-                s->bpp.error = dupprintf("Incorrect MAC received on packet");
+                ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
                 crStopV;
             }
 
@@ -360,8 +363,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
                 (s->len + 4) % s->cipherblk != 0) {
-                s->bpp.error = dupprintf(
-                    "Incoming packet was garbled on decryption");
+                ssh_sw_abort(s->bpp.ssh,
+                             "Incoming packet was garbled on decryption");
                 crStopV;
             }
 
@@ -398,15 +401,15 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             if (s->in.mac && !ssh2_mac_verify(
                     s->in.mac, s->data, s->len + 4, s->in.sequence)) {
-                s->bpp.error = dupprintf("Incorrect MAC received on packet");
+                ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
                 crStopV;
             }
         }
         /* Get and sanity-check the amount of random padding. */
         s->pad = s->data[4];
         if (s->pad < 4 || s->len - s->pad < 1) {
-            s->bpp.error = dupprintf(
-                "Invalid padding length on received packet");
+            ssh_sw_abort(s->bpp.ssh,
+                         "Invalid padding length on received packet");
             crStopV;
         }
         /*
@@ -494,8 +497,6 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
             int type = s->pktin->type;
             s->pktin = NULL;
 
-            if (type == SSH2_MSG_DISCONNECT)
-                s->bpp.seen_disconnect = TRUE;
             if (type == SSH2_MSG_NEWKEYS) {
                 /*
                  * Mild layer violation: in this situation we must
@@ -508,6 +509,15 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
             }
         }
     }
+
+  eof:
+    if (!s->bpp.expect_close) {
+        ssh_remote_error(s->bpp.ssh,
+                         "Server unexpectedly closed network connection");
+    } else {
+        ssh_remote_eof(s->bpp.ssh, "Server closed network connection");
+    }
+
     crFinishV;
 }
 

+ 2501 - 0
source/putty/ssh2connection.c

@@ -0,0 +1,2501 @@
+/*
+ * Packet protocol layer for the SSH-2 connection protocol (RFC 4254).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshchan.h"
+#include "sshcr.h"
+
+struct ssh2_channel;
+
+typedef enum MainChanType {
+    MAINCHAN_DIRECT_TCPIP, MAINCHAN_SESSION, MAINCHAN_NONE
+} MainChanType;
+
+struct outstanding_global_request;
+
+struct ssh2_connection_state {
+    int crState;
+
+    Ssh ssh;
+
+    ssh_sharing_state *connshare;
+    char *peer_verstring;
+
+    struct ssh2_channel *mainchan;     /* primary session channel */
+    MainChanType mctype;
+    char *mainchan_open_error;
+    int mainchan_ready;
+    int echoedit;
+    int mainchan_eof_pending, mainchan_eof_sent;
+    int session_attempt, session_status;
+    int term_width, term_height, term_width_orig, term_height_orig;
+    int want_user_input;
+
+    int ssh_is_simple;
+
+    Conf *conf;
+
+    tree234 *channels;		       /* indexed by local id */
+    int all_channels_throttled;
+
+    int X11_fwd_enabled;
+    struct X11Display *x11disp;
+    struct X11FakeAuth *x11auth;
+    tree234 *x11authtree;
+
+    int got_pty;
+    int agent_fwd_enabled;
+
+    tree234 *rportfwds;
+    PortFwdManager *portfwdmgr;
+    int portfwdmgr_configured;
+
+    /*
+     * These store the list of global requests that we're waiting for
+     * replies to. (REQUEST_FAILURE doesn't come with any indication
+     * of what message caused it, so we have to keep track of the
+     * queue ourselves.)
+     */
+    struct outstanding_global_request *globreq_head, *globreq_tail;
+
+    ConnectionLayer cl;
+    PacketProtocolLayer ppl;
+};
+
+static int ssh2_rportfwd_cmp(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)
+	return -1;
+    return 0;
+}
+
+static void ssh2_connection_free(PacketProtocolLayer *); 
+static void ssh2_connection_process_queue(PacketProtocolLayer *);
+static int ssh2_connection_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg);
+static int ssh2_connection_want_user_input(PacketProtocolLayer *ppl);
+static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl);
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const struct PacketProtocolLayerVtable ssh2_connection_vtable = {
+    ssh2_connection_free,
+    ssh2_connection_process_queue,
+    ssh2_connection_get_specials,
+    ssh2_connection_special_cmd,
+    ssh2_connection_want_user_input,
+    ssh2_connection_got_user_input,
+    ssh2_connection_reconfigure,
+    "ssh-connection",
+};
+
+static struct ssh_rportfwd *ssh2_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx);
+static void ssh2_rportfwd_remove(
+    ConnectionLayer *cl, struct ssh_rportfwd *rpf);
+static SshChannel *ssh2_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *org, Channel *chan);
+static struct X11FakeAuth *ssh2_add_sharing_x11_display(
+    ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+    share_channel *share_chan);
+static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl,
+                                            struct X11FakeAuth *auth);
+static void ssh2_send_packet_from_downstream(
+    ConnectionLayer *cl, unsigned id, int type,
+    const void *pkt, int pktlen, const char *additional_log_text);
+static unsigned ssh2_alloc_sharing_channel(
+    ConnectionLayer *cl, ssh_sharing_connstate *connstate);
+static void ssh2_delete_sharing_channel(
+    ConnectionLayer *cl, unsigned localid);
+static void ssh2_sharing_queue_global_request(
+    ConnectionLayer *cl, ssh_sharing_connstate *share_ctx);
+static int ssh2_agent_forwarding_permitted(ConnectionLayer *cl);
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height);
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, int bufsize);
+static int ssh2_stdin_backlog(ConnectionLayer *cl);
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, int throttled);
+static int ssh2_ldisc_option(ConnectionLayer *cl, int option);
+
+static const struct ConnectionLayerVtable ssh2_connlayer_vtable = {
+    ssh2_rportfwd_alloc,
+    ssh2_rportfwd_remove,
+    ssh2_lportfwd_open,
+    ssh2_add_sharing_x11_display,
+    ssh2_remove_sharing_x11_display,
+    ssh2_send_packet_from_downstream,
+    ssh2_alloc_sharing_channel,
+    ssh2_delete_sharing_channel,
+    ssh2_sharing_queue_global_request,
+    ssh2_agent_forwarding_permitted,
+    ssh2_terminal_size,
+    ssh2_stdout_unthrottle,
+    ssh2_stdin_backlog,
+    ssh2_throttle_all_channels,
+    ssh2_ldisc_option,
+};
+
+static char *ssh2_channel_open_failure_error_text(PktIn *pktin)
+{
+    static const char *const reasons[] = {
+        NULL,
+        "Administratively prohibited",
+        "Connect failed",
+        "Unknown channel type",
+        "Resource shortage",
+    };
+    unsigned reason_code;
+    const char *reason_code_string;
+    char reason_code_buf[256];
+    ptrlen reason;
+
+    reason_code = get_uint32(pktin);
+    if (reason_code < lenof(reasons) && reasons[reason_code]) {
+        reason_code_string = reasons[reason_code];
+    } else {
+        reason_code_string = reason_code_buf;
+        sprintf(reason_code_buf, "unknown reason code %#x", reason_code);
+    }
+
+    reason = get_string(pktin);
+
+    return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason));
+}
+
+struct outstanding_channel_request;
+struct outstanding_global_request;
+
+struct ssh2_channel {
+    struct ssh2_connection_state *connlayer;
+
+    unsigned remoteid, localid;
+    int type;
+    /* True if we opened this channel but server hasn't confirmed. */
+    int halfopen;
+
+    /* Bitmap of whether we've sent/received CHANNEL_EOF and
+     * CHANNEL_CLOSE. */
+#define CLOSES_SENT_EOF    1
+#define CLOSES_SENT_CLOSE  2
+#define CLOSES_RCVD_EOF    4
+#define CLOSES_RCVD_CLOSE  8
+    int closes;
+
+    /*
+     * This flag indicates that an EOF is pending on the outgoing side
+     * of the channel: that is, wherever we're getting the data for
+     * this channel has sent us some data followed by EOF. We can't
+     * actually send the EOF until we've finished sending the data, so
+     * we set this flag instead to remind us to do so once our buffer
+     * is clear.
+     */
+    int pending_eof;
+
+    /*
+     * True if this channel is causing the underlying connection to be
+     * throttled.
+     */
+    int throttling_conn;
+
+    /*
+     * True if we currently have backed-up data on the direction of
+     * this channel pointing out of the SSH connection, and therefore
+     * would prefer the 'Channel' implementation not to read further
+     * local input if possible.
+     */
+    int throttled_by_backlog;
+
+    bufchain outbuffer;
+    unsigned remwindow, remmaxpkt;
+    /* locwindow is signed so we can cope with excess data. */
+    int locwindow, locmaxwin;
+    /*
+     * remlocwin is the amount of local window that we think
+     * the remote end had available to it after it sent the
+     * last data packet or window adjust ack.
+     */
+    int remlocwin;
+
+    /*
+     * These store the list of channel requests that we're waiting for
+     * replies to. (CHANNEL_FAILURE doesn't come with any indication
+     * of what message caused it, so we have to keep track of the
+     * queue ourselves.)
+     */
+    struct outstanding_channel_request *chanreq_head, *chanreq_tail;
+
+    enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+
+    ssh_sharing_connstate *sharectx; /* sharing context, if this is a
+                                      * downstream channel */
+    Channel *chan;      /* handle the client side of this channel, if not */
+    SshChannel sc;      /* entry point for chan to talk back to */
+};
+
+static int ssh2channel_write(SshChannel *c, const void *buf, int len);
+static void ssh2channel_write_eof(SshChannel *c);
+static void ssh2channel_unclean_close(SshChannel *c, const char *err);
+static void ssh2channel_unthrottle(SshChannel *c, int bufsize);
+static Conf *ssh2channel_get_conf(SshChannel *c);
+static void ssh2channel_window_override_removed(SshChannel *c);
+static void ssh2channel_x11_sharing_handover(
+    SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+    const char *peer_addr, int peer_port, int endian,
+    int protomajor, int protominor, const void *initial_data, int initial_len);
+
+static const struct SshChannelVtable ssh2channel_vtable = {
+    ssh2channel_write,
+    ssh2channel_write_eof,
+    ssh2channel_unclean_close,
+    ssh2channel_unthrottle,
+    ssh2channel_get_conf,
+    ssh2channel_window_override_removed,
+    ssh2channel_x11_sharing_handover,
+};
+
+typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);
+
+static void ssh2_channel_init(struct ssh2_channel *c);
+static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);
+static PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+                                 cr_handler_fn_t handler, void *ctx);
+static void ssh2_channel_check_close(struct ssh2_channel *c);
+static void ssh2_channel_try_eof(struct ssh2_channel *c);
+static void ssh2_set_window(struct ssh2_channel *c, int newwin);
+static int ssh2_try_send(struct ssh2_channel *c);
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c);
+static void ssh2_channel_check_throttle(struct ssh2_channel *c);
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+                                     const char *reason);
+static void ssh2_channel_destroy(struct ssh2_channel *c);
+
+static void ssh2_check_termination(struct ssh2_connection_state *s);
+
+typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
+                                PktIn *pktin, void *ctx);
+struct outstanding_global_request {
+    gr_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_global_request *next;
+};
+static void ssh2_queue_global_request_handler(
+    struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx)
+{
+    struct outstanding_global_request *ogr =
+        snew(struct outstanding_global_request);
+    ogr->handler = handler;
+    ogr->ctx = ctx;
+    if (s->globreq_tail)
+        s->globreq_tail->next = ogr;
+    else
+        s->globreq_head = ogr;
+    s->globreq_tail = ogr;
+}
+
+typedef struct mainchan {
+    struct ssh2_connection_state *connlayer;
+    SshChannel *sc;
+
+    Channel chan;
+} mainchan;
+static mainchan *mainchan_new(struct ssh2_connection_state *s);
+static void ssh2_setup_x11(struct ssh2_channel *c, PktIn *pktin, void *ctx);
+static void ssh2_setup_agent(struct ssh2_channel *c, PktIn *pktin, void *ctx);
+static void ssh2_setup_pty(struct ssh2_channel *c, PktIn *pktin, void *ctx);
+static void ssh2_setup_env(struct ssh2_channel *c, PktIn *pktin, void *ctx);
+static void ssh2_response_session(struct ssh2_channel *c, PktIn *, void *);
+
+static int ssh2_channelcmp(void *av, void *bv)
+{
+    const struct ssh2_channel *a = (const struct ssh2_channel *) av;
+    const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+    if (a->localid < b->localid)
+	return -1;
+    if (a->localid > b->localid)
+	return +1;
+    return 0;
+}
+
+static int ssh2_channelfind(void *av, void *bv)
+{
+    const unsigned *a = (const unsigned *) av;
+    const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+    if (*a < b->localid)
+	return -1;
+    if (*a > b->localid)
+	return +1;
+    return 0;
+}
+
+/*
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
+ */
+struct outstanding_channel_request {
+    cr_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_channel_request *next;
+};
+
+static void ssh2_channel_free(struct ssh2_channel *c)
+{
+    bufchain_clear(&c->outbuffer);
+    while (c->chanreq_head) {
+        struct outstanding_channel_request *chanreq = c->chanreq_head;
+        c->chanreq_head = c->chanreq_head->next;
+        sfree(chanreq);
+    }
+    if (c->chan)
+        chan_free(c->chan);
+    sfree(c);
+}
+
+PacketProtocolLayer *ssh2_connection_new(
+    Ssh ssh, ssh_sharing_state *connshare, int is_simple,
+    Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out)
+{
+    struct ssh2_connection_state *s = snew(struct ssh2_connection_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh2_connection_vtable;
+
+    s->conf = conf_copy(conf);
+
+    s->ssh_is_simple = is_simple;
+
+    s->connshare = connshare;
+    s->peer_verstring = dupstr(peer_verstring);
+
+    s->channels = newtree234(ssh2_channelcmp);
+
+    s->x11authtree = newtree234(x11_authcmp);
+
+    /* Need to get the frontend for s->cl now, because we won't be
+     * helpfully notified when a copy is written into s->ppl by our
+     * owner. */
+    s->cl.vt = &ssh2_connlayer_vtable;
+    s->cl.frontend = ssh_get_frontend(ssh);
+
+    s->portfwdmgr = portfwdmgr_new(&s->cl);
+    s->rportfwds = newtree234(ssh2_rportfwd_cmp);
+
+    *cl_out = &s->cl;
+    if (s->connshare)
+        ssh_connshare_provide_connlayer(s->connshare, &s->cl);
+
+    return &s->ppl;
+}
+
+static void ssh2_connection_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+    struct X11FakeAuth *auth;
+    struct ssh2_channel *c;
+    struct ssh_rportfwd *rpf;
+
+    sfree(s->peer_verstring);
+
+    conf_free(s->conf);
+
+    sfree(s->mainchan_open_error);
+
+    while ((c = delpos234(s->channels, 0)) != NULL)
+        ssh2_channel_free(c);
+    freetree234(s->channels);
+
+    if (s->x11disp)
+	x11_free_display(s->x11disp);
+    while ((auth = delpos234(s->x11authtree, 0)) != NULL)
+        x11_free_fake_auth(auth);
+    freetree234(s->x11authtree);
+
+    while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+        free_rportfwd(rpf);
+    freetree234(s->rportfwds);
+    portfwdmgr_free(s->portfwdmgr);
+
+    sfree(s);
+}
+
+static int ssh2_connection_filter_queue(struct ssh2_connection_state *s)
+{
+    PktIn *pktin;
+    PktOut *pktout;
+    ptrlen type, data;
+    struct ssh2_channel *c;
+    struct outstanding_channel_request *ocr;
+    unsigned localid, remid, winsize, pktsize, ext_type;
+    int want_reply, reply_type, expect_halfopen;
+    const char *error;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    /* Cross-reference to ssh2transport.c to handle the common packets
+     * between login and connection: DISCONNECT, DEBUG and IGNORE. If
+     * we have an instance of ssh2transport below us, then those
+     * messages won't come here anyway, but they could if we're
+     * running in bare ssh2-connection mode. */
+    extern int ssh2_common_filter_queue(PacketProtocolLayer *ppl);
+
+    while (1) {
+        if (ssh2_common_filter_queue(&s->ppl))
+            return TRUE;
+        if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+            return FALSE;
+
+        switch (pktin->type) {
+          case SSH2_MSG_GLOBAL_REQUEST:
+            /* type = */ get_string(pktin);
+            want_reply = get_bool(pktin);
+
+            /*
+             * 'reply_type' is the message type we'll send in
+             * response, if want_reply is set. Initialise it to the
+             * default value of REQUEST_FAILURE, for any request we
+             * don't recognise and handle below.
+             */
+            reply_type = SSH2_MSG_REQUEST_FAILURE;
+
+            /*
+             * We currently don't support any incoming global requests
+             * at all. Here's where to insert some code to handle
+             * them, if and when we do.
+             */
+
+            if (want_reply) {
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, reply_type);
+                pq_push(s->ppl.out_pq, pktout);
+            }
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_REQUEST_SUCCESS:
+          case SSH2_MSG_REQUEST_FAILURE:
+            if (!s->globreq_head) {
+                ssh_proto_error(
+                    s->ppl.ssh,
+                    "Received %s with no outstanding global request",
+                    ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx,
+                                  pktin->type));
+                return TRUE;
+            }
+
+            s->globreq_head->handler(s, pktin, s->globreq_head->ctx);
+            {
+                struct outstanding_global_request *tmp = s->globreq_head;
+                s->globreq_head = s->globreq_head->next;
+                sfree(tmp);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_CHANNEL_OPEN:
+            error = NULL;
+
+            type = get_string(pktin);
+            c = snew(struct ssh2_channel);
+            c->connlayer = s;
+
+            remid = get_uint32(pktin);
+            winsize = get_uint32(pktin);
+            pktsize = get_uint32(pktin);
+
+            if (ptrlen_eq_string(type, "x11")) {
+                char *addrstr = mkstr(get_string(pktin));
+                int peerport = get_uint32(pktin);
+
+                ppl_logevent(("Received X11 connect request from %s:%d",
+                              addrstr, peerport));
+
+                if (!s->X11_fwd_enabled && !s->connshare) {
+                    error = "X11 forwarding is not enabled";
+                } else {
+                    c->chan = x11_new_channel(
+                        s->x11authtree, &c->sc, addrstr, peerport,
+                        s->connshare != NULL);
+                    ppl_logevent(("Opened X11 forward channel"));
+                }
+
+                sfree(addrstr);
+            } else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
+                struct ssh_rportfwd pf, *realpf;
+                ptrlen peeraddr;
+                int peerport;
+
+                pf.shost = mkstr(get_string(pktin));
+                pf.sport = get_uint32(pktin);
+                peeraddr = get_string(pktin);
+                peerport = get_uint32(pktin);
+                realpf = find234(s->rportfwds, &pf, NULL);
+                ppl_logevent(("Received remote port %s:%d open request "
+                              "from %.*s:%d", pf.shost, pf.sport,
+                              PTRLEN_PRINTF(peeraddr), peerport));
+                sfree(pf.shost);
+
+                if (realpf == NULL) {
+                    error = "Remote port is not recognised";
+                } else {
+                    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,
+                            BinarySource_UPCAST(pktin)->data,
+                            BinarySource_UPCAST(pktin)->len);
+                        sfree(c);
+                        break;
+                    }
+
+                    err = portfwdmgr_connect(
+                        s->portfwdmgr, &c->chan, realpf->dhost, realpf->dport,
+                        &c->sc, realpf->addressfamily);
+                    ppl_logevent(("Attempting to forward remote port to "
+                                  "%s:%d", realpf->dhost, realpf->dport));
+                    if (err != NULL) {
+                        ppl_logevent(("Port open failed: %s", err));
+                        sfree(err);
+                        error = "Port open failed";
+                    } else {
+                        ppl_logevent(("Forwarded port opened successfully"));
+                    }
+                }
+            } else if (ptrlen_eq_string(type, "[email protected]")) {
+                if (!s->agent_fwd_enabled)
+                    error = "Agent forwarding is not enabled";
+                else
+                    c->chan = agentf_new(&c->sc);
+            } else {
+                error = "Unsupported channel type requested";
+            }
+
+            c->remoteid = remid;
+            c->halfopen = FALSE;
+            if (error) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, SSH2_OPEN_CONNECT_FAILED);
+                put_stringz(pktout, error);
+                put_stringz(pktout, "en");	/* language tag */
+                pq_push(s->ppl.out_pq, pktout);
+                ppl_logevent(("Rejected channel open: %s", error));
+                sfree(c);
+            } else {
+                ssh2_channel_init(c);
+                c->remwindow = winsize;
+                c->remmaxpkt = pktsize;
+                if (c->chan->initial_fixed_window_size) {
+                    c->locwindow = c->locmaxwin = c->remlocwin =
+                        c->chan->initial_fixed_window_size;
+                }
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, c->localid);
+                put_uint32(pktout, c->locwindow);
+                put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+                pq_push(s->ppl.out_pq, pktout);
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          case SSH2_MSG_CHANNEL_DATA:
+          case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+          case SSH2_MSG_CHANNEL_REQUEST:
+          case SSH2_MSG_CHANNEL_EOF:
+          case SSH2_MSG_CHANNEL_CLOSE:
+          case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+          case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+          case SSH2_MSG_CHANNEL_SUCCESS:
+          case SSH2_MSG_CHANNEL_FAILURE:
+            /*
+             * Common preliminary code for all the messages from the
+             * server that cite one of our channel ids: look up that
+             * channel id, check it exists, and if it's for a sharing
+             * downstream, pass it on.
+             */
+            localid = get_uint32(pktin);
+            c = find234(s->channels, &localid, ssh2_channelfind);
+
+            if (c && c->sharectx) {
+                share_got_pkt_from_server(c->sharectx, pktin->type,
+                                          BinarySource_UPCAST(pktin)->data,
+                                          BinarySource_UPCAST(pktin)->len);
+                pq_pop(s->ppl.in_pq);
+                break;
+            }
+
+            expect_halfopen = (
+                pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ||
+                pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE);
+
+            if (!c || c->halfopen != expect_halfopen) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Received %s for %s channel %u",
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type),
+                                (!c ? "nonexistent" :
+                                 c->halfopen ? "half-open" : "open"),
+                                localid);
+                return TRUE;
+            }
+ 
+            switch (pktin->type) {
+              case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+                assert(c->halfopen);
+                c->remoteid = get_uint32(pktin);
+                c->halfopen = FALSE;
+                c->remwindow = get_uint32(pktin);
+                c->remmaxpkt = get_uint32(pktin);
+
+                chan_open_confirmation(c->chan);
+
+                /*
+                 * Now that the channel is fully open, it's possible
+                 * in principle to immediately close it. Check whether
+                 * it wants us to!
+                 *
+                 * This can occur if a local socket error occurred
+                 * between us sending out CHANNEL_OPEN and receiving
+                 * OPEN_CONFIRMATION. If that happens, all we can do
+                 * is immediately initiate close proceedings now that
+                 * we know the server's id to put in the close
+                 * message. We'll have handled that in this code by
+                 * having already turned c->chan into a zombie, so its
+                 * want_close method (which ssh2_channel_check_close
+                 * will consult) will already be returning TRUE.
+                 */
+                ssh2_channel_check_close(c);
+
+                if (c->pending_eof)
+                    ssh2_channel_try_eof(c); /* in case we had a pending EOF */
+                break;
+
+              case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+                assert(c->halfopen);
+
+                {
+                    char *err = ssh2_channel_open_failure_error_text(pktin);
+                    chan_open_failed(c->chan, err);
+                    sfree(err);
+                }
+                chan_free(c->chan);
+
+                del234(s->channels, c);
+                ssh2_channel_free(c);
+
+                break;
+
+              case SSH2_MSG_CHANNEL_DATA:
+              case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+                ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 :
+                            get_uint32(pktin));
+                data = get_string(pktin);
+                if (!get_err(pktin)) {
+                    int bufsize;
+                    c->locwindow -= data.len;
+                    c->remlocwin -= data.len;
+                    if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR)
+                        data.len = 0; /* ignore unknown extended data */
+                    bufsize = chan_send(
+                        c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR,
+                        data.ptr, data.len);
+
+                    /*
+                     * 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 larger window.
+                     */
+                    if (c->remlocwin <= 0 &&
+                        c->throttle_state == UNTHROTTLED &&
+                        c->locmaxwin < 0x40000000)
+                        c->locmaxwin += OUR_V2_WINSIZE;
+
+                    /*
+                     * If we are not buffering too much data, enlarge
+                     * the window again at the remote side. If we are
+                     * buffering too much, we may still need to adjust
+                     * the window if the server's sent excess data.
+                     */
+                    if (bufsize < c->locmaxwin)
+                        ssh2_set_window(c, c->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, throttle the whole channel.
+                     */
+                    if ((bufsize > c->locmaxwin ||
+                         (s->ssh_is_simple && bufsize>0)) &&
+                        !c->throttling_conn) {
+                        c->throttling_conn = TRUE;
+                        ssh_throttle_conn(s->ppl.ssh, +1);
+                    }
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+                if (!(c->closes & CLOSES_SENT_EOF)) {
+                    c->remwindow += get_uint32(pktin);
+                    ssh2_try_send_and_unthrottle(c);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_REQUEST:
+                type = get_string(pktin);
+                want_reply = get_bool(pktin);
+
+                /*
+                 * 'reply_type' is the message type we'll send in
+                 * response, if want_reply is set. Initialise it to
+                 * the default value of CHANNEL_FAILURE, for any
+                 * request we don't recognise and handle below.
+                 */
+                reply_type = SSH2_MSG_CHANNEL_FAILURE;
+
+                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 we
+                 * recognise.
+                 */
+                if (c == s->mainchan) {
+                    int exitcode;
+
+                    /*
+                     * We recognise "exit-status" and "exit-signal" on
+                     * the primary channel.
+                     */
+                    if (ptrlen_eq_string(type, "exit-status")) {
+                        exitcode = toint(get_uint32(pktin));
+                        ssh_got_exitcode(s->ppl.ssh, exitcode);
+                        ppl_logevent(("Server sent command exit status %d",
+                                      exitcode));
+                        reply_type = SSH2_MSG_CHANNEL_SUCCESS;
+                    } else if (ptrlen_eq_string(type, "exit-signal")) {
+                        char *fmt_sig = NULL, *fmt_msg = NULL;
+                        ptrlen errmsg;
+                        int core = FALSE;
+                        int format;
+
+                        /*
+                         * ICK: older versions of OpenSSH (e.g. 3.4p1)
+                         * provide an `int' for the signal, despite
+                         * its having been a `string' in the drafts of
+                         * RFC 4254 since at least 2001. (Fixed in
+                         * session.c 1.147.) Try to infer which we can
+                         * safely parse it as.
+                         */
+
+                        size_t startpos = BinarySource_UPCAST(pktin)->pos;
+
+                        for (format = 0; format < 2; format++) {
+                            BinarySource_UPCAST(pktin)->pos = startpos;
+                            BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR;
+
+                            if (format == 0) {
+                                /* standard string-based format */
+                                ptrlen signame = get_string(pktin);
+                                fmt_sig = dupprintf(" \"%.*s\"",
+                                                    PTRLEN_PRINTF(signame));
+
+                                /*
+                                 * Really hideous method of translating the
+                                 * signal description back into a locally
+                                 * meaningful number.
+                                 */
+
+                                if (0)
+                                    ;
+#define TRANSLATE_SIGNAL(s)                                             \
+                                else if (ptrlen_eq_string(signame, #s)) \
+                                    exitcode = 128 + SIG ## s
+#ifdef SIGABRT
+                                TRANSLATE_SIGNAL(ABRT);
+#endif
+#ifdef SIGALRM
+                                TRANSLATE_SIGNAL(ALRM);
+#endif
+#ifdef SIGFPE
+                                TRANSLATE_SIGNAL(FPE);
+#endif
+#ifdef SIGHUP
+                                TRANSLATE_SIGNAL(HUP);
+#endif
+#ifdef SIGILL
+                                TRANSLATE_SIGNAL(ILL);
+#endif
+#ifdef SIGINT
+                                TRANSLATE_SIGNAL(INT);
+#endif
+#ifdef SIGKILL
+                                TRANSLATE_SIGNAL(KILL);
+#endif
+#ifdef SIGPIPE
+                                TRANSLATE_SIGNAL(PIPE);
+#endif
+#ifdef SIGQUIT
+                                TRANSLATE_SIGNAL(QUIT);
+#endif
+#ifdef SIGSEGV
+                                TRANSLATE_SIGNAL(SEGV);
+#endif
+#ifdef SIGTERM
+                                TRANSLATE_SIGNAL(TERM);
+#endif
+#ifdef SIGUSR1
+                                TRANSLATE_SIGNAL(USR1);
+#endif
+#ifdef SIGUSR2
+                                TRANSLATE_SIGNAL(USR2);
+#endif
+#undef TRANSLATE_SIGNAL
+                                else
+                                    exitcode = 128;
+                            } else {
+                                /* nonstandard integer format */
+                                unsigned signum = get_uint32(pktin);
+                                fmt_sig = dupprintf(" %u", signum);
+                                exitcode = 128 + signum;
+                            }
+
+                            core = get_bool(pktin);
+                            errmsg = get_string(pktin); /* error message */
+                            get_string(pktin);     /* language tag */
+                            if (!get_err(pktin) && get_avail(pktin) == 0)
+                                break;             /* successful parse */
+
+                            sfree(fmt_sig);
+                        }
+
+                        if (format == 2) {
+                            fmt_sig = NULL;
+                            exitcode = 128;
+                        }
+
+                        if (errmsg.len) {
+                            fmt_msg = dupprintf(" (\"%.*s\")",
+                                                PTRLEN_PRINTF(errmsg));
+                        }
+
+                        ssh_got_exitcode(s->ppl.ssh, exitcode);
+                        ppl_logevent(("Server exited on signal%s%s%s",
+                                      fmt_sig ? fmt_sig : "",
+                                      core ? " (core dumped)" : "",
+                                      fmt_msg ? fmt_msg : ""));
+                        sfree(fmt_sig);
+                        sfree(fmt_msg);
+                        reply_type = SSH2_MSG_CHANNEL_SUCCESS;
+                    }
+                }
+                if (want_reply) {
+                    pktout = ssh_bpp_new_pktout(s->ppl.bpp, reply_type);
+                    put_uint32(pktout, c->remoteid);
+                    pq_push(s->ppl.out_pq, pktout);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_SUCCESS:
+              case SSH2_MSG_CHANNEL_FAILURE:
+                ocr = c->chanreq_head;
+                if (!ocr) {
+                    ssh_proto_error(
+                        s->ppl.ssh,
+                        "Received %s for channel %d with no outstanding "
+                        "channel request",
+                        ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                      s->ppl.bpp->pls->actx, pktin->type));
+                    return TRUE;
+                }
+                ocr->handler(c, pktin, ocr->ctx);
+                c->chanreq_head = ocr->next;
+                sfree(ocr);
+                /*
+                 * We may now initiate channel-closing procedures, if
+                 * that CHANNEL_REQUEST was the last thing outstanding
+                 * before we send CHANNEL_CLOSE.
+                 */
+                ssh2_channel_check_close(c);
+                break;
+
+              case SSH2_MSG_CHANNEL_EOF:
+                if (!(c->closes & CLOSES_RCVD_EOF)) {
+                    c->closes |= CLOSES_RCVD_EOF;
+                    chan_send_eof(c->chan);
+                    ssh2_channel_check_close(c);
+                }
+                break;
+
+              case SSH2_MSG_CHANNEL_CLOSE:
+                /*
+                 * When we receive CLOSE on a channel, we assume it
+                 * comes with an implied EOF if we haven't seen EOF
+                 * yet.
+                 */
+                if (!(c->closes & CLOSES_RCVD_EOF)) {
+                    c->closes |= CLOSES_RCVD_EOF;
+                    chan_send_eof(c->chan);
+                }
+
+                if (!(s->ppl.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->chanreq_head) {
+                        struct outstanding_channel_request *ocr =
+                            c->chanreq_head;
+                        ocr->handler(c, NULL, ocr->ctx);
+                        c->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 the remote side is doing
+                 * away with the entire channel. (If it had wanted to
+                 * send us EOF and continue receiving data from us, it
+                 * would have just sent CHANNEL_EOF.)
+                 */
+                if (!(c->closes & CLOSES_SENT_EOF)) {
+                    /*
+                     * 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->outbuffer);
+
+                    /*
+                     * Send outgoing EOF.
+                     */
+                    sshfwd_write_eof(&c->sc);
+
+                    /*
+                     * Make sure we don't read any more from whatever
+                     * our local data source is for this channel.
+                     * (This will pick up on the changes made by
+                     * sshfwd_write_eof.)
+                     */
+                    ssh2_channel_check_throttle(c);
+                }
+
+                /*
+                 * Now process the actual close.
+                 */
+                if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+                    c->closes |= CLOSES_RCVD_CLOSE;
+                    ssh2_channel_check_close(c);
+                }
+
+                break;
+            }
+
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          default:
+            return FALSE;
+        }
+    }
+}
+
+static void ssh2_handle_winadj_response(struct ssh2_channel *c,
+					PktIn *pktin, void *ctx)
+{
+    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->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
+     * complete.
+     */
+    if (c->throttle_state == UNTHROTTLING)
+	c->throttle_state = UNTHROTTLED;
+}
+
+static void ssh2_set_window(struct ssh2_channel *c, int newwin)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+
+    /*
+     * Never send WINDOW_ADJUST for a channel that the remote side has
+     * already sent EOF on; there's no point, since it won't be
+     * sending any more data anyway. Ditto if _we've_ already sent
+     * CLOSE.
+     */
+    if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
+	return;
+
+    /*
+     * If the client-side Channel is in an initial setup phase with a
+     * fixed window size, e.g. for an X11 channel when we're still
+     * waiting to see its initial auth and may yet hand it off to a
+     * downstream, don't send any WINDOW_ADJUST either.
+     */
+    if (c->chan->initial_fixed_window_size)
+        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
+     * window as well).
+     */
+    if ((s->ppl.remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
+	newwin = OUR_V2_MAXPKT;
+
+    /*
+     * Only send a WINDOW_ADJUST if there's significantly more window
+     * available than the other end thinks there is.  This saves us
+     * sending a WINDOW_ADJUST for every character in a shell session.
+     *
+     * "Significant" is arbitrarily defined as half the window size.
+     */
+    if (newwin / 2 >= c->locwindow) {
+	PktOut *pktout;
+	unsigned *up;
+
+	/*
+	 * In order to keep track of how much window the client
+	 * actually has available, we'd like it to acknowledge each
+	 * WINDOW_ADJUST.  We can't do that directly, so we accompany
+	 * it with a CHANNEL_REQUEST that has to be acknowledged.
+	 *
+	 * 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.
+	 */
+	if (newwin == c->locmaxwin &&
+            !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+	    up = snew(unsigned);
+	    *up = newwin - c->locwindow;
+	    pktout = ssh2_chanreq_init(c, "[email protected]",
+				       ssh2_handle_winadj_response, up);
+	    pq_push(s->ppl.out_pq, pktout);
+
+	    if (c->throttle_state != UNTHROTTLED)
+		c->throttle_state = UNTHROTTLING;
+	} else {
+	    /* Pretend the WINDOW_ADJUST was acked immediately. */
+	    c->remlocwin = newwin;
+	    c->throttle_state = THROTTLED;
+	}
+	pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+	put_uint32(pktout, c->remoteid);
+	put_uint32(pktout, newwin - c->locwindow);
+	pq_push(s->ppl.out_pq, pktout);
+	c->locwindow = newwin;
+    }
+}
+
+static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s)
+{
+    ssh2_connection_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+    PktIn *pktin;
+    PktOut *pktout;
+
+    if (ssh2_connection_filter_queue(s)) /* no matter why we were called */
+        return;
+
+    crBegin(s->crState);
+
+    /*
+     * Create the main session channel, if any.
+     */
+    if (conf_get_int(s->conf, CONF_ssh_no_shell)) {
+        s->mctype = MAINCHAN_NONE;
+    } else if (*conf_get_str(s->conf, CONF_ssh_nc_host)) {
+        s->mctype = MAINCHAN_DIRECT_TCPIP;
+    } else {
+        s->mctype = MAINCHAN_SESSION;
+    }
+
+    if (s->mctype != MAINCHAN_NONE) {
+        mainchan *mc = mainchan_new(s);
+
+        switch (s->mctype) {
+          case MAINCHAN_NONE:
+            assert(0 && "Unreachable");
+            break;
+
+          case MAINCHAN_SESSION:
+            s->mainchan = snew(struct ssh2_channel);
+            mc->sc = &s->mainchan->sc;
+            s->mainchan->connlayer = s;
+            ssh2_channel_init(s->mainchan);
+            s->mainchan->chan = &mc->chan;
+            s->mainchan->halfopen = TRUE;
+	    pktout = ssh2_chanopen_init(s->mainchan, "session");
+	    ppl_logevent(("Opening session as main channel"));
+	    pq_push(s->ppl.out_pq, pktout);
+            break;
+
+          case MAINCHAN_DIRECT_TCPIP:
+            mc->sc = ssh_lportfwd_open(
+                &s->cl, conf_get_str(s->conf, CONF_ssh_nc_host),
+                conf_get_int(s->conf, CONF_ssh_nc_port),
+                "main channel", &mc->chan);
+            s->mainchan = FROMFIELD(mc->sc, struct ssh2_channel, sc);
+            break;
+	}
+
+        /*
+         * Wait until that channel has been successfully opened (or
+         * not).
+         */
+	crMaybeWaitUntilV(!s->mainchan || !s->mainchan->halfopen);
+        if (!s->mainchan) {
+            ssh_sw_abort(s->ppl.ssh, "Server refused to open main channel: %s",
+                         s->mainchan_open_error);
+            return;
+        }
+    }
+
+    /*
+     * 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 (s->connshare)
+        share_activate(s->connshare, s->peer_verstring);
+
+    if (s->mainchan && s->ssh_is_simple) {
+	/*
+	 * 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.
+	 */
+	pktout = ssh2_chanreq_init(
+            s->mainchan, "[email protected]", NULL, NULL);
+	pq_push(s->ppl.out_pq, pktout);
+    }
+
+    /*
+     * Enable port forwardings.
+     */
+    portfwdmgr_config(s->portfwdmgr, s->conf);
+    s->portfwdmgr_configured = TRUE;
+
+    if (s->mainchan && s->mctype == MAINCHAN_SESSION) {
+	/*
+	 * Send the CHANNEL_REQUESTS for the main session channel.
+	 * Each one is handled by its own little asynchronous
+	 * co-routine.
+	 */
+
+	/* Potentially enable X11 forwarding. */
+	if (conf_get_int(s->conf, CONF_x11_forward)) {
+            s->x11disp = x11_setup_display(
+                conf_get_str(s->conf, CONF_x11_display), s->conf);
+            if (!s->x11disp) {
+                /* FIXME: return an error message from x11_setup_display */
+                ppl_logevent(("X11 forwarding not enabled: unable to"
+                              " initialise X display"));
+            } else {
+                s->x11auth = x11_invent_fake_auth(
+                    s->x11authtree, conf_get_int(s->conf, CONF_x11_auth));
+                s->x11auth->disp = s->x11disp;
+
+                ssh2_setup_x11(s->mainchan, NULL, NULL);
+            }
+        }
+
+	/* Potentially enable agent forwarding. */
+	if (ssh_agent_forwarding_permitted(&s->cl))
+	    ssh2_setup_agent(s->mainchan, NULL, NULL);
+
+	/* Now allocate a pty for the session. */
+	if (!conf_get_int(s->conf, CONF_nopty))
+	    ssh2_setup_pty(s->mainchan, NULL, NULL);
+
+	/* Send environment variables. */
+	ssh2_setup_env(s->mainchan, NULL, 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.
+	 */
+	for (s->session_attempt = 0; s->session_attempt < 2;
+             s->session_attempt++) {
+	    int subsys;
+	    char *cmd;
+
+	    if (s->session_attempt == 0) {
+		subsys = conf_get_int(s->conf, CONF_ssh_subsys);
+		cmd = conf_get_str(s->conf, CONF_remote_cmd);
+	    } else {
+		subsys = conf_get_int(s->conf, CONF_ssh_subsys2);
+		cmd = conf_get_str(s->conf, CONF_remote_cmd2);
+                if (!*cmd)
+                    break;
+                ppl_logevent(("Primary command failed; attempting fallback"));
+	    }
+
+	    if (subsys) {
+		pktout = ssh2_chanreq_init(s->mainchan, "subsystem",
+                                           ssh2_response_session, s);
+		put_stringz(pktout, cmd);
+	    } else if (*cmd) {
+		pktout = ssh2_chanreq_init(s->mainchan, "exec",
+                                           ssh2_response_session, s);
+		put_stringz(pktout, cmd);
+	    } else {
+		pktout = ssh2_chanreq_init(s->mainchan, "shell",
+                                           ssh2_response_session, s);
+	    }
+	    pq_push(s->ppl.out_pq, pktout);
+            s->session_status = 0;
+
+            /* Wait for success or failure message to be passed to
+             * ssh2_response_session, which will set session_status to
+             * +1 for success or -1 for failure */
+	    crMaybeWaitUntilV(s->session_status != 0);
+
+	    if (s->session_status > 0) {
+                if (s->session_attempt == 1)
+                    ssh_got_fallback_cmd(s->ppl.ssh);
+		ppl_logevent(("Started a shell/command"));
+                break;
+            }
+        }
+
+        if (s->session_status < 0) {
+            /*
+             * We failed to start either the primary or the fallback
+             * command.
+             */
+            ssh_sw_abort(s->ppl.ssh,
+                         "Server refused to start a shell/command");
+            return;
+	}
+    } else {
+	s->echoedit = TRUE;
+    }
+
+    s->mainchan_ready = TRUE;
+    if (s->mainchan)
+	s->want_user_input = TRUE;
+
+    /* If an EOF or a window-size change arrived before we were ready
+     * to handle either one, handle them now. */
+    if (s->mainchan_eof_pending)
+	ssh_ppl_special_cmd(&s->ppl, SS_EOF, 0);
+    if (s->term_width_orig != s->term_width ||
+        s->term_height_orig != s->term_height)
+	ssh_terminal_size(&s->cl, s->term_width, s->term_height);
+
+    ssh_ldisc_update(s->ppl.ssh);
+
+    /*
+     * Transfer data!
+     */
+
+    while (1) {
+	if ((pktin = ssh2_connection_pop(s)) != NULL) {
+
+	    /*
+	     * _All_ the connection-layer packets we expect to
+	     * receive are now handled by the dispatch table.
+	     * Anything that reaches here must be bogus.
+	     */
+
+            ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer "
+                            "packet, type %d (%s)", pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+	}
+	crReturnV;
+    }
+
+    crFinishV;
+}
+
+static void ssh2_channel_check_close(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    if (c->halfopen) {
+        /*
+         * 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) ||
+         chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF),
+                         (c->closes & CLOSES_RCVD_EOF))) &&
+	!c->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 = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE);
+	put_uint32(pktout, c->remoteid);
+	pq_push(s->ppl.out_pq, pktout);
+        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
+    }
+
+    if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+	assert(c->chanreq_head == NULL);
+        /*
+         * We have both sent and received CLOSE, which means we're
+         * completely done with the channel.
+         */
+        ssh2_channel_destroy(c);
+    }
+}
+
+static void ssh2_channel_try_eof(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+    assert(c->pending_eof);          /* precondition for calling us */
+    if (c->halfopen)
+        return;                 /* can't close: not even opened yet */
+    if (bufchain_size(&c->outbuffer) > 0)
+        return;              /* can't send EOF: pending outgoing data */
+
+    c->pending_eof = FALSE;            /* we're about to send it */
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF);
+    put_uint32(pktout, c->remoteid);
+    pq_push(s->ppl.out_pq, pktout);
+    c->closes |= CLOSES_SENT_EOF;
+    ssh2_channel_check_close(c);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static int ssh2_try_send(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+    int bufsize;
+
+    while (c->remwindow > 0 && bufchain_size(&c->outbuffer) > 0) {
+	int len;
+	void *data;
+	bufchain_prefix(&c->outbuffer, &data, &len);
+	if ((unsigned)len > c->remwindow)
+	    len = c->remwindow;
+	if ((unsigned)len > c->remmaxpkt)
+	    len = c->remmaxpkt;
+	pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA);
+	put_uint32(pktout, c->remoteid);
+        put_string(pktout, data, len);
+        pq_push(s->ppl.out_pq, pktout);
+	bufchain_consume(&c->outbuffer, len);
+	c->remwindow -= len;
+    }
+
+    /*
+     * After having sent as much data as we can, return the amount
+     * still buffered.
+     */
+    bufsize = bufchain_size(&c->outbuffer);
+
+    /*
+     * And if there's no data pending but we need to send an EOF, send
+     * it.
+     */
+    if (!bufsize && c->pending_eof)
+        ssh2_channel_try_eof(c);
+
+    return bufsize;
+}
+
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c)
+{
+    int bufsize;
+    if (c->closes & CLOSES_SENT_EOF)
+	return;                   /* don't send on channels we've EOFed */
+    bufsize = ssh2_try_send(c);
+    if (bufsize == 0) {
+        c->throttled_by_backlog = FALSE;
+        ssh2_channel_check_throttle(c);
+    }
+}
+
+static void ssh2_channel_check_throttle(struct ssh2_channel *c)
+{
+    /*
+     * We don't want this channel to read further input if this
+     * particular channel has a backed-up SSH window, or if the
+     * outgoing side of the whole SSH connection is currently
+     * throttled, or if this channel already has an outgoing EOF
+     * either sent or pending.
+     */
+    chan_set_input_wanted(c->chan,
+                          !c->throttled_by_backlog &&
+                          !c->connlayer->all_channels_throttled &&
+                          !c->pending_eof &&
+                          !(c->closes & CLOSES_SENT_EOF));
+}
+
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel.  This converts the channel into a zombie.
+ */
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+                                     const char *reason)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    char *msg = NULL;
+
+    if (c->sharectx)
+        return;
+
+    msg = chan_log_close_msg(c->chan);
+
+    if (msg)
+        ppl_logevent(("%s%s%s", msg, reason ? " " : "", reason ? reason : ""));
+
+    sfree(msg);
+
+    chan_free(c->chan);
+    c->chan = zombiechan_new();
+}
+
+static void ssh2_check_termination_callback(void *vctx)
+{
+    struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx;
+    ssh2_check_termination(s);
+}
+
+static void ssh2_channel_destroy(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+
+    assert(c->chanreq_head == NULL);
+
+    ssh2_channel_close_local(c, NULL);
+    del234(s->channels, c);
+    ssh2_channel_free(c);
+
+    /*
+     * If that was the last channel left open, we might need to
+     * terminate. But we'll be a bit cautious, by doing that in a
+     * toplevel callback, just in case anything on the current call
+     * stack objects to this entire PPL being freed.
+     */
+    queue_toplevel_callback(ssh2_check_termination_callback, s);
+}
+
+static void ssh2_check_termination(struct ssh2_connection_state *s)
+{
+    /*
+     * Decide whether we should terminate the SSH connection now.
+     * Called after a channel or a downstream goes away. The general
+     * policy is that we terminate when none of either is left.
+     */
+
+    if (s->mctype == MAINCHAN_NONE) {
+        /*
+         * Exception: in ssh_no_shell mode we persist even in the
+         * absence of any channels (because our purpose is probably to
+         * be a background port forwarder).
+         */
+        return;
+    }
+
+    if (count234(s->channels) == 0 &&
+        !(s->connshare && share_ndownstreams(s->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_user_close(s->ppl.ssh, "All channels closed");
+        return;
+    }
+}
+
+static void ssh2_setup_x11(struct ssh2_channel *c, PktIn *pktin, void *ctx)
+{
+    struct ssh2_setup_x11_state {
+	int crLine;
+    };
+    struct ssh2_connection_state *cs = c->connlayer;
+    PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent */
+    PktOut *pktout;
+    crStateP(ssh2_setup_x11_state, ctx);
+
+    crBeginState;
+
+    ppl_logevent(("Requesting X11 forwarding"));
+    pktout = ssh2_chanreq_init(cs->mainchan, "x11-req", ssh2_setup_x11, s);
+    put_bool(pktout, 0);	       /* many connections */
+    put_stringz(pktout, cs->x11auth->protoname);
+    put_stringz(pktout, cs->x11auth->datastring);
+    put_uint32(pktout, cs->x11disp->screennum);
+    pq_push(cs->ppl.out_pq, 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) {
+            ppl_logevent(("X11 forwarding enabled"));
+            cs->X11_fwd_enabled = TRUE;
+        } else
+            ppl_logevent(("X11 forwarding refused"));
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_agent(struct ssh2_channel *c, PktIn *pktin, void *ctx)
+{
+    struct ssh2_setup_agent_state {
+	int crLine;
+    };
+    struct ssh2_connection_state *cs = c->connlayer;
+    PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent */
+    PktOut *pktout;
+    crStateP(ssh2_setup_agent_state, ctx);
+
+    crBeginState;
+
+    ppl_logevent(("Requesting OpenSSH-style agent forwarding"));
+    pktout = ssh2_chanreq_init(cs->mainchan, "[email protected]",
+                               ssh2_setup_agent, s);
+    pq_push(cs->ppl.out_pq, 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) {
+            ppl_logevent(("Agent forwarding enabled"));
+            cs->agent_fwd_enabled = TRUE;
+        } else
+            ppl_logevent(("Agent forwarding refused"));
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_pty(struct ssh2_channel *c, PktIn *pktin, void *ctx)
+{
+    struct ssh2_setup_pty_state {
+	int crLine;
+        int ospeed, ispeed;
+    };
+    struct ssh2_connection_state *cs = c->connlayer;
+    PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent, ppl_printf */
+    PktOut *pktout;
+    crStateP(ssh2_setup_pty_state, ctx);
+
+    crBeginState;
+
+    /* Unpick the terminal-speed string. */
+    s->ospeed = 38400; s->ispeed = 38400; /* last-resort defaults */
+    sscanf(conf_get_str(cs->conf, CONF_termspeed), "%d,%d",
+           &s->ospeed, &s->ispeed);
+    /* Build the pty request. */
+    pktout = ssh2_chanreq_init(cs->mainchan, "pty-req", ssh2_setup_pty, s);
+    put_stringz(pktout, conf_get_str(cs->conf, CONF_termtype));
+    put_uint32(pktout, cs->term_width);
+    put_uint32(pktout, cs->term_height);
+    cs->term_width_orig = cs->term_width;
+    cs->term_height_orig = cs->term_height;
+    put_uint32(pktout, 0);	       /* pixel width */
+    put_uint32(pktout, 0);	       /* pixel height */
+    {
+        strbuf *modebuf = strbuf_new();
+        write_ttymodes_to_packet_from_conf(
+            BinarySink_UPCAST(modebuf), cs->ppl.frontend, cs->conf,
+            2, s->ospeed, s->ispeed);
+        put_stringsb(pktout, modebuf);
+    }
+    pq_push(cs->ppl.out_pq, 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) {
+            ppl_logevent(("Allocated pty (ospeed %dbps, ispeed %dbps)",
+                          s->ospeed, s->ispeed));
+            cs->got_pty = TRUE;
+        } else {
+            ppl_printf(("Server refused to allocate pty\r\n"));
+            cs->echoedit = TRUE;
+        }
+    }
+
+    crFinishFreeV;
+}
+
+static void ssh2_setup_env(struct ssh2_channel *c, PktIn *pktin, void *ctx)
+{
+    struct ssh2_setup_env_state {
+	int crLine;
+	int num_env, env_left, env_ok;
+    };
+    struct ssh2_connection_state *cs = c->connlayer;
+    PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent, ppl_printf */
+    PktOut *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(cs->conf, CONF_environmt, NULL, &key);
+	     val != NULL;
+	     val = conf_get_str_strs(cs->conf, CONF_environmt, key, &key)) {
+	    pktout = ssh2_chanreq_init(cs->mainchan, "env", ssh2_setup_env, s);
+	    put_stringz(pktout, key);
+	    put_stringz(pktout, val);
+	    pq_push(cs->ppl.out_pq, pktout);
+
+	    s->num_env++;
+	}
+	if (s->num_env)
+	    ppl_logevent(("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) {
+	    ppl_logevent(("All environment variables successfully set"));
+	} else if (s->env_ok == 0) {
+	    ppl_logevent(("All environment variables refused"));
+	    ppl_printf(("Server refused to set environment variables\r\n"));
+	} else {
+	    ppl_logevent(("%d environment variables refused",
+                          s->num_env - s->env_ok));
+	    ppl_printf(("Server refused to set all environment "
+                        "variables\r\n"));
+	}
+    }
+  out:;
+    crFinishFreeV;
+}
+
+static void ssh2_response_session(struct ssh2_channel *c, PktIn *pktin,
+                                  void *ctx)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    s->session_status = (pktin->type == SSH2_MSG_CHANNEL_SUCCESS ? +1 : -1);
+}
+
+/*
+ * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves
+ * chan untouched (since it will sometimes have been filled in before
+ * calling this).
+ */
+static void ssh2_channel_init(struct ssh2_channel *c)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    c->closes = 0;
+    c->pending_eof = FALSE;
+    c->throttling_conn = FALSE;
+    c->sharectx = NULL;
+    c->locwindow = c->locmaxwin = c->remlocwin =
+        s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+    c->chanreq_head = NULL;
+    c->throttle_state = UNTHROTTLED;
+    bufchain_init(&c->outbuffer);
+    c->sc.vt = &ssh2channel_vtable;
+    c->localid = alloc_channel_id(s->channels, struct ssh2_channel);
+    add234(s->channels, c);
+}
+
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN);
+    put_stringz(pktout, type);
+    put_uint32(pktout, c->localid);
+    put_uint32(pktout, c->locwindow);     /* our window size */
+    put_uint32(pktout, OUR_V2_MAXPKT);    /* our max pkt size */
+    return pktout;
+}
+
+/*
+ * 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 PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+                                 cr_handler_fn_t handler, void *ctx)
+{
+    struct ssh2_connection_state *s = c->connlayer;
+    PktOut *pktout;
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST);
+    put_uint32(pktout, c->remoteid);
+    put_stringz(pktout, type);
+    put_bool(pktout, handler != NULL);
+    if (handler != NULL) {
+        struct outstanding_channel_request *ocr =
+            snew(struct outstanding_channel_request);
+
+        ocr->handler = handler;
+        ocr->ctx = ctx;
+        ocr->next = NULL;
+        if (!c->chanreq_head)
+            c->chanreq_head = ocr;
+        else
+            c->chanreq_tail->next = ocr;
+        c->chanreq_tail = ocr;
+    }
+    return pktout;
+}
+
+static Conf *ssh2channel_get_conf(SshChannel *sc)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    return s->conf;
+}
+
+static void ssh2channel_write_eof(SshChannel *sc)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+
+    if (c->closes & CLOSES_SENT_EOF)
+        return;
+
+    c->pending_eof = TRUE;
+    ssh2_channel_try_eof(c);
+}
+
+static void ssh2channel_unclean_close(SshChannel *sc, const char *err)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    char *reason;
+
+    reason = dupprintf("due to local error: %s", err);
+    ssh2_channel_close_local(c, reason);
+    sfree(reason);
+    c->pending_eof = FALSE;   /* this will confuse a zombie channel */
+
+    ssh2_channel_check_close(c);
+}
+
+static void ssh2channel_unthrottle(SshChannel *sc, int bufsize)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    int buflimit;
+
+    buflimit = s->ssh_is_simple ? 0 : c->locmaxwin;
+    if (bufsize < buflimit)
+        ssh2_set_window(c, buflimit - bufsize);
+
+    if (c->throttling_conn && bufsize <= buflimit) {
+	c->throttling_conn = 0;
+	ssh_throttle_conn(s->ppl.ssh, -1);
+    }
+}
+
+static int ssh2channel_write(SshChannel *sc, const void *buf, int len)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    assert(!(c->closes & CLOSES_SENT_EOF));
+    bufchain_add(&c->outbuffer, buf, len);
+    return ssh2_try_send(c);
+}
+
+static void ssh2channel_x11_sharing_handover(
+    SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+    const char *peer_addr, int peer_port, int endian,
+    int protomajor, int protominor, const void *initial_data, int initial_len)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    /*
+     * 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 sharing one, meaning
+     * that we completely stop tracking windows and buffering data and
+     * just pass more or less unmodified SSH messages back and forth.
+     */
+    c->sharectx = share_cs;
+    share_setup_x11_channel(share_cs, share_chan,
+                            c->localid, c->remoteid, c->remwindow,
+                            c->remmaxpkt, c->locwindow,
+                            peer_addr, peer_port, endian,
+                            protomajor, protominor,
+                            initial_data, initial_len);
+    chan_free(c->chan);
+    c->chan = NULL;
+}
+
+static void ssh2channel_window_override_removed(SshChannel *sc)
+{
+    struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    /*
+     * This function is called when a client-side Channel has just
+     * stopped requiring an initial fixed-size window.
+     */
+    assert(!c->chan->initial_fixed_window_size);
+    ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
+static SshChannel *ssh2_lportfwd_open(
+    ConnectionLayer *cl, const char *hostname, int port,
+    const char *org, Channel *chan)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh2_channel *c = snew(struct ssh2_channel);
+    PktOut *pktout;
+
+    c->connlayer = s;
+    ssh2_channel_init(c);
+    c->halfopen = TRUE;
+    c->chan = chan;
+
+    ppl_logevent(("Opening connection to %s:%d for %s", hostname, port, org));
+
+    pktout = ssh2_chanopen_init(c, "direct-tcpip");
+    {
+        char *trimmed_host = host_strduptrim(hostname);
+        put_stringz(pktout, trimmed_host);
+        sfree(trimmed_host);
+    }
+    put_uint32(pktout, port);
+    /*
+     * We make up values for the originator data; partly it's too much
+     * hassle to keep track, and partly I'm not convinced the server
+     * should be told details like that about my local network
+     * configuration. The "originator IP address" is syntactically a
+     * numeric IP address, and some servers (e.g., Tectia) get upset
+     * if it doesn't match this syntax.
+     */
+    put_stringz(pktout, "0.0.0.0");
+    put_uint32(pktout, 0);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return &c->sc;
+}
+
+static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s,
+                                           PktIn *pktin, void *ctx)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+    if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) {
+	ppl_logevent(("Remote port forwarding from %s enabled",
+                      rpf->log_description));
+    } else {
+	ppl_logevent(("Remote port forwarding from %s refused",
+                      rpf->log_description));
+
+	struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+	assert(realpf == rpf);
+        portfwdmgr_close(s->portfwdmgr, rpf->pfr);
+	free_rportfwd(rpf);
+    }
+}
+
+static struct ssh_rportfwd *ssh2_rportfwd_alloc(
+    ConnectionLayer *cl,
+    const char *shost, int sport, const char *dhost, int dport,
+    int addressfamily, const char *log_description, PortFwdRecord *pfr,
+    ssh_sharing_connstate *share_ctx)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+    rpf->shost = dupstr(shost);
+    rpf->sport = sport;
+    rpf->dhost = dupstr(dhost);
+    rpf->dport = dport;
+    rpf->addressfamily = addressfamily;
+    rpf->log_description = dupstr(log_description);
+    rpf->pfr = pfr;
+    rpf->share_ctx = share_ctx;
+
+    if (add234(s->rportfwds, rpf) != rpf) {
+        free_rportfwd(rpf);
+        return NULL;
+    }
+
+    if (!rpf->share_ctx) {
+        PktOut *pktout = ssh_bpp_new_pktout(
+            s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+        put_stringz(pktout, "tcpip-forward");
+        put_bool(pktout, 1);       /* want reply */
+        put_stringz(pktout, rpf->shost);
+        put_uint32(pktout, rpf->sport);
+        pq_push(s->ppl.out_pq, pktout);
+
+        ssh2_queue_global_request_handler(
+            s, ssh2_rportfwd_globreq_response, rpf);
+    }
+
+    return rpf;
+}
+
+static void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+
+    if (rpf->share_ctx) {
+        /*
+         * We don't manufacture a cancel-tcpip-forward message for
+         * remote port forwardings being removed on behalf of a
+         * downstream; we just pass through the one the downstream
+         * sent to us.
+         */
+    } else {
+        PktOut *pktout = ssh_bpp_new_pktout(
+            s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+        put_stringz(pktout, "cancel-tcpip-forward");
+        put_bool(pktout, 0);           /* _don't_ want reply */
+        put_stringz(pktout, rpf->shost);
+        put_uint32(pktout, rpf->sport);
+        pq_push(s->ppl.out_pq, pktout);
+    }
+
+    struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+    assert(realpf == rpf);
+    free_rportfwd(rpf);
+}
+
+static void ssh2_sharing_globreq_response(
+    struct ssh2_connection_state *s, PktIn *pktin, void *ctx)
+{
+    ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx;
+    share_got_pkt_from_server(cs, pktin->type,
+                              BinarySource_UPCAST(pktin)->data,
+                              BinarySource_UPCAST(pktin)->len);
+}
+
+static void ssh2_sharing_queue_global_request(
+    ConnectionLayer *cl, ssh_sharing_connstate *cs)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs);
+}
+
+static struct X11FakeAuth *ssh2_add_sharing_x11_display(
+    ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+    share_channel *share_chan)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    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(s->x11authtree, authtype);
+    auth->share_cs = share_cs;
+    auth->share_chan = share_chan;
+
+    return auth;
+}
+
+static void ssh2_remove_sharing_x11_display(
+    ConnectionLayer *cl, struct X11FakeAuth *auth)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    del234(s->x11authtree, auth);
+    x11_free_fake_auth(auth);
+}
+
+static unsigned ssh2_alloc_sharing_channel(
+    ConnectionLayer *cl, ssh_sharing_connstate *connstate)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c = snew(struct ssh2_channel);
+
+    c->connlayer = s;
+    ssh2_channel_init(c);
+    c->chan = NULL;
+    c->sharectx = connstate;
+    return c->localid;
+}
+
+static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind);
+    if (c)
+        ssh2_channel_destroy(c);
+}
+
+static void ssh2_send_packet_from_downstream(
+        ConnectionLayer *cl, unsigned id, int type,
+        const void *data, int datalen, const char *additional_log_text)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type);
+    pkt->downstream_id = id;
+    pkt->additional_log_text = additional_log_text;
+    put_data(pkt, data, datalen);
+    pq_push(s->ppl.out_pq, pkt);
+}
+
+static int ssh2_agent_forwarding_permitted(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    return conf_get_int(s->conf, CONF_agentfwd) && agent_exists();
+}
+
+static void mainchan_free(Channel *chan);
+static void mainchan_open_confirmation(Channel *chan);
+static void mainchan_open_failure(Channel *chan, const char *errtext);
+static int mainchan_send(Channel *chan, int is_stderr, const void *, int);
+static void mainchan_send_eof(Channel *chan);
+static void mainchan_set_input_wanted(Channel *chan, int wanted);
+static char *mainchan_log_close_msg(Channel *chan);
+
+static const struct ChannelVtable mainchan_channelvt = {
+    mainchan_free,
+    mainchan_open_confirmation,
+    mainchan_open_failure,
+    mainchan_send,
+    mainchan_send_eof,
+    mainchan_set_input_wanted,
+    mainchan_log_close_msg,
+    chan_no_eager_close,
+};
+
+static mainchan *mainchan_new(struct ssh2_connection_state *s)
+{
+    mainchan *mc = snew(mainchan);
+    mc->connlayer = s;
+    mc->sc = NULL;
+    mc->chan.vt = &mainchan_channelvt;
+    mc->chan.initial_fixed_window_size = 0;
+    return mc;
+}
+
+static void mainchan_free(Channel *chan)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+    s->mainchan = NULL;
+    sfree(mc);
+}
+
+static void mainchan_open_confirmation(Channel *chan)
+{
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    update_specials_menu(s->ppl.frontend);
+    ppl_logevent(("Opened main channel"));
+}
+
+static void mainchan_open_failure(Channel *chan, const char *errtext)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+
+    /*
+     * Record the failure reason we're given, and let the main
+     * coroutine handle closing the SSH session.
+     */
+    s->mainchan_open_error = dupstr(errtext);
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static int mainchan_send(Channel *chan, int is_stderr,
+                         const void *data, int length)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+    return from_backend(s->ppl.frontend, is_stderr, data, length);
+}
+
+static void mainchan_send_eof(Channel *chan)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    if (!s->mainchan_eof_sent &&
+        (from_backend_eof(s->ppl.frontend) || s->got_pty)) {
+        /*
+         * Either from_backend_eof told us that the front end wants us
+         * to close the outgoing side of the connection as soon as we
+         * see EOF from the far end, or else we've unilaterally
+         * decided to do that because we've allocated a remote pty and
+         * hence EOF isn't a particularly meaningful concept.
+         */
+        sshfwd_write_eof(mc->sc);
+        ppl_logevent(("Sent EOF message"));
+    }
+    s->mainchan_eof_sent = TRUE;
+    s->want_user_input = FALSE;      /* now stop reading from stdin */
+}
+
+static void mainchan_set_input_wanted(Channel *chan, int wanted)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = FROMFIELD(chan, mainchan, chan);
+    struct ssh2_connection_state *s = mc->connlayer;
+
+    /*
+     * This is the main channel of the SSH session, i.e. the one tied
+     * to the standard input (or GUI) of the primary SSH client user
+     * interface. So ssh->send_ok is how we control whether we're
+     * reading from that input.
+     */
+    s->want_user_input = wanted;
+}
+
+static char *mainchan_log_close_msg(Channel *chan)
+{
+    return dupstr("Main session channel closed");
+}
+
+/*
+ * List of signal names defined by RFC 4254. These include all the ISO
+ * C signals, but are a subset of the POSIX required signals.
+ *
+ * The list macro takes parameters MAIN and SUB, which is an arbitrary
+ * UI decision to expose the signals we think users are most likely to
+ * want, with extra descriptive text, and relegate the less probable
+ * ones to a submenu for people who know what they're doing.
+ */
+#define SIGNAL_LIST(MAIN, SUB)                  \
+    MAIN(INT, "Interrupt")                      \
+    MAIN(TERM, "Terminate")                     \
+    MAIN(KILL, "Kill")                          \
+    MAIN(QUIT, "Quit")                          \
+    MAIN(HUP, "Hangup")                         \
+    SUB(ABRT)                                   \
+    SUB(ALRM)                                   \
+    SUB(FPE)                                    \
+    SUB(ILL)                                    \
+    SUB(PIPE)                                   \
+    SUB(SEGV)                                   \
+    SUB(USR1)                                   \
+    SUB(USR2)                                   \
+    /* end of list */
+
+static int ssh2_connection_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+    int toret = FALSE;
+
+    if (s->mainchan) {
+	add_special(ctx, "Break", SS_BRK, 0);
+
+        #define ADD_MAIN(name, desc) \
+        add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
+        #define ADD_SUB(name) \
+	add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
+
+        #define NO_ADD_SUB(name)
+        #define NO_ADD_MAIN(name, desc)
+
+        SIGNAL_LIST(ADD_MAIN, NO_ADD_SUB);
+	add_special(ctx, "More signals", SS_SUBMENU, 0);
+        SIGNAL_LIST(NO_ADD_MAIN, ADD_SUB);
+	add_special(ctx, NULL, SS_EXITMENU, 0);
+
+        #undef ADD_MAIN
+        #undef ADD_SUB
+        #undef NO_ADD_MAIN
+        #undef NO_ADD_SUB
+
+        toret = TRUE;
+    }
+
+    /*
+     * Don't bother offering IGNORE if we've decided the remote
+     * won't cope with it, since we wouldn't bother sending it if
+     * asked anyway.
+     */
+    if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+        if (toret)
+            add_special(ctx, NULL, SS_SEP, 0);
+
+        add_special(ctx, "IGNORE message", SS_NOP, 0);
+        toret = TRUE;
+    }
+
+    return toret;
+}
+
+static const char *ssh_signal_lookup(SessionSpecialCode code)
+{
+    #define CHECK_SUB(name) \
+    if (code == SS_SIG ## name) return #name;
+    #define CHECK_MAIN(name, desc) CHECK_SUB(name)
+
+    SIGNAL_LIST(CHECK_MAIN, CHECK_SUB);
+    return NULL;
+
+    #undef CHECK_MAIN
+    #undef CHECK_SUB
+}
+
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+                                        SessionSpecialCode code, int arg)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+    PktOut *pktout;
+    const char *signame;
+
+    if (code == SS_PING || code == SS_NOP) {
+        if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE);
+            put_stringz(pktout, "");
+            pq_push(s->ppl.out_pq, pktout);
+	}
+    } else if (code == SS_EOF) {
+	if (!s->mainchan_ready) {
+	    /*
+	     * Buffer the EOF to send as soon as the main channel is
+	     * fully set up.
+	     */
+            s->mainchan_eof_pending = TRUE;
+	} else if (s->mainchan && !s->mainchan_eof_sent) {
+            sshfwd_write_eof(&s->mainchan->sc);
+	}
+    } else if (code == SS_BRK) {
+        if (s->mainchan) {
+	    pktout = ssh2_chanreq_init(s->mainchan, "break", NULL, NULL);
+	    put_uint32(pktout, 0);   /* default break length */
+            pq_push(s->ppl.out_pq, pktout);
+	}
+    } else if ((signame = ssh_signal_lookup(code)) != NULL) {
+        /* It's a signal. */
+        if (s->mainchan) {
+            pktout = ssh2_chanreq_init(s->mainchan, "signal", NULL, NULL);
+            put_stringz(pktout, signame);
+            pq_push(s->ppl.out_pq, pktout);
+            ppl_logevent(("Sent signal SIG%s", signame));
+        }
+    }
+}
+
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+
+    s->term_width = width;
+    s->term_height = height;
+
+    if (s->mainchan_ready) {
+        PktOut *pktout = ssh2_chanreq_init(
+            s->mainchan, "window-change", NULL, NULL);
+        put_uint32(pktout, s->term_width);
+        put_uint32(pktout, s->term_height);
+        put_uint32(pktout, 0);
+        put_uint32(pktout, 0);
+	pq_push(s->ppl.out_pq, pktout);
+    }
+}
+
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, int bufsize)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+
+    if (s->mainchan)
+        ssh2channel_unthrottle(&s->mainchan->sc, bufsize);
+}
+
+static int ssh2_stdin_backlog(ConnectionLayer *cl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+
+    return s->mainchan ? bufchain_size(&s->mainchan->outbuffer) : 0;
+}
+
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, int throttled)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+    struct ssh2_channel *c;
+    int i;
+
+    s->all_channels_throttled = throttled;
+
+    for (i = 0; NULL != (c = index234(s->channels, i)); i++)
+        ssh2_channel_check_throttle(c);
+}
+
+static int ssh2_ldisc_option(ConnectionLayer *cl, int option)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(cl, struct ssh2_connection_state, cl);
+
+    /* We always return the same value for LD_ECHO and LD_EDIT */
+    return s->echoedit;
+}
+
+static int ssh2_connection_want_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+    return s->want_user_input;
+}
+
+static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+
+    while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) {
+        /*
+         * Add user input to the main channel's buffer.
+         */
+        void *data;
+        int len;
+        bufchain_prefix(s->ppl.user_input, &data, &len);
+        sshfwd_write(&s->mainchan->sc, data, len);
+        bufchain_consume(s->ppl.user_input, len);
+    }
+}
+
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_connection_state *s =
+        FROMFIELD(ppl, struct ssh2_connection_state, ppl);
+
+    conf_free(s->conf);
+    s->conf = conf_copy(conf);
+
+    if (s->portfwdmgr_configured)
+        portfwdmgr_config(s->portfwdmgr, s->conf);
+}

+ 2967 - 0
source/putty/ssh2transport.c

@@ -0,0 +1,2967 @@
+/*
+ * Packet protocol layer for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshcr.h"
+#include "storage.h"
+
+#ifndef NO_GSSAPI
+#include "sshgssc.h"
+#include "sshgss.h"
+#define MIN_CTXT_LIFETIME 5     /* Avoid rekey with short lifetime (seconds) */
+#define GSS_KEX_CAPABLE (1<<0)  /* Can do GSS KEX */
+#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
+#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
+#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
+#endif
+
+#define DH_MIN_SIZE 1024
+#define DH_MAX_SIZE 8192
+
+enum kexlist {
+    KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER,
+    KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP,
+    NKEXLIST
+};
+#define MAXKEXLIST 16
+struct kexinit_algorithm {
+    const char *name;
+    union {
+        struct {
+            const struct ssh_kex *kex;
+            int warn;
+        } kex;
+        struct {
+            const ssh_keyalg *hostkey;
+            int warn;
+        } hk;
+        struct {
+            const struct ssh2_cipheralg *cipher;
+            int warn;
+        } cipher;
+        struct {
+            const struct ssh2_macalg *mac;
+            int etm;
+        } mac;
+        const struct ssh_compression_alg *comp;
+    } u;
+};
+
+struct ssh_signkey_with_user_pref_id {
+    const ssh_keyalg *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 ssh2_macalg *const macs[] = {
+    &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+};
+const static struct ssh2_macalg *const buggymacs[] = {
+    &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
+};
+
+static ssh_compressor *ssh_comp_none_init(void)
+{
+    return NULL;
+}
+static void ssh_comp_none_cleanup(ssh_compressor *handle)
+{
+}
+static ssh_decompressor *ssh_decomp_none_init(void)
+{
+    return NULL;
+}
+static void ssh_decomp_none_cleanup(ssh_decompressor *handle)
+{
+}
+static void ssh_comp_none_block(ssh_compressor *handle,
+                                unsigned char *block, int len,
+                                unsigned char **outblock, int *outlen,
+                                int minlen)
+{
+}
+static int ssh_decomp_none_block(ssh_decompressor *handle,
+                                 unsigned char *block, int len,
+                                 unsigned char **outblock, int *outlen)
+{
+    return 0;
+}
+const static struct ssh_compression_alg ssh_comp_none = {
+    "none", NULL,
+    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+    ssh_decomp_none_init, ssh_decomp_none_cleanup, ssh_decomp_none_block,
+    NULL
+};
+const static struct ssh_compression_alg *const compressions[] = {
+    &ssh_zlib, &ssh_comp_none
+};
+
+/*
+ * Enumeration of high-level classes of reason why we might need to do
+ * a repeat key exchange. A full detailed reason in human-readable
+ * string form for the Event Log is also provided, but this enum type
+ * is used to discriminate between classes of reason that the code
+ * needs to treat differently.
+ *
+ * RK_NONE == 0 is the value indicating that no rekey is currently
+ * needed at all. RK_INITIAL indicates that we haven't even done the
+ * _first_ key exchange yet. RK_SERVER indicates that we're rekeying
+ * because the server asked for it, not because we decided it
+ * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates
+ * that we're rekeying because we've just got new GSSAPI credentials
+ * (hence there's no point in doing a preliminary check for new GSS
+ * creds, because we already know the answer); RK_POST_USERAUTH
+ * indicates that _if_ we're going to need a post-userauth immediate
+ * rekey for any reason, this is the moment to do it.
+ *
+ * So RK_POST_USERAUTH only tells the transport layer to _consider_
+ * rekeying, not to definitely do it. Also, that one enum value is
+ * special in that the user-readable reason text is passed in to the
+ * transport layer as NULL, whereas fills in the reason text after it
+ * decides whether it needs a rekey at all. In the other cases,
+ * rekey_reason is passed in to the at the same time as rekey_class.
+ */
+typedef enum RekeyClass {
+    RK_NONE = 0,
+    RK_INITIAL,
+    RK_SERVER,
+    RK_NORMAL,
+    RK_POST_USERAUTH,
+    RK_GSS_UPDATE
+} RekeyClass;
+
+struct ssh2_transport_state {
+    int crState;
+
+    PacketProtocolLayer *higher_layer;
+    PktInQueue pq_in_higher;
+    PktOutQueue pq_out_higher;
+    IdempotentCallback ic_pq_out_higher;
+
+    Conf *conf;
+    char *savedhost;
+    int savedport;
+    const char *rekey_reason;
+    enum RekeyClass rekey_class;
+
+    unsigned long max_data_size;
+
+    const struct ssh_kex *kex_alg;
+    const ssh_keyalg *hostkey_alg;
+    char *hostkey_str; /* string representation, for easy checking in rekeys */
+    unsigned char session_id[SSH2_KEX_MAX_HASH_LEN];
+    int session_id_len;
+    struct dh_ctx *dh_ctx;
+    ssh_hash *exhash;
+
+    struct DataTransferStats *stats;
+
+    char *client_greeting, *server_greeting;
+
+    int kex_in_progress;
+    unsigned long next_rekey, last_rekey;
+    const char *deferred_rekey_reason;
+    int higher_layer_ok;
+
+    /*
+     * Fully qualified host name, which we need if doing GSSAPI.
+     */
+    char *fullhostname;
+
+    /* shgss is outside the ifdef on purpose to keep APIs simple. If
+     * NO_GSSAPI is not defined, then it's just an opaque structure
+     * tag and the pointer will be NULL. */
+    struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+    int gss_status;
+    time_t gss_cred_expiry;             /* Re-delegate if newer */
+    unsigned long gss_ctxt_lifetime;    /* Re-delegate when short */
+    tree234 *transient_hostkey_cache;
+#endif
+
+    int gss_kex_used;
+
+    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 ssh2_macalg *const *maclist;
+    int nmacs;
+    struct {
+        const struct ssh2_cipheralg *cipher;
+        const struct ssh2_macalg *mac;
+        int etm_mode;
+        const struct ssh_compression_alg *comp;
+    } in, out;
+    ptrlen hostkeydata, sigdata;
+    char *keystr, *fingerprint;
+    ssh_key *hkey;                     /* actual host key */
+    struct RSAKey *rsa_kex_key;             /* for RSA kex */
+    struct ec_key *ecdh_key;              /* for ECDH kex */
+    unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
+    int n_preferred_kex;
+    int can_gssapi_keyex;
+    int need_gss_transient_hostkey;
+    int warned_about_no_gss_transient_hostkey;
+    const struct ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */
+    int n_preferred_hk;
+    int preferred_hk[HK_MAX];
+    int n_preferred_ciphers;
+    const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+    const struct ssh_compression_alg *preferred_comp;
+    int userauth_succeeded;         /* for delayed compression */
+    int pending_compression;
+    int got_session_id;
+    int dlgret;
+    int guessok;
+    int ignorepkt;
+    struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
+#ifndef NO_GSSAPI
+    Ssh_gss_buf gss_buf;
+    Ssh_gss_buf gss_rcvtok, gss_sndtok;
+    Ssh_gss_stat gss_stat;
+    Ssh_gss_buf mic;
+    int init_token_sent;
+    int complete_rcvd;
+    int gss_delegate;
+#endif
+
+    /*
+     * List of host key algorithms for which we _don't_ have a stored
+     * host key. These are indices into the main hostkey_algs[] array
+     */
+    int uncert_hostkeys[lenof(hostkey_algs)];
+    int n_uncert_hostkeys;
+
+    /*
+     * Flag indicating that the current rekey is intended to finish
+     * with a newly cross-certified host key.
+     */
+    int cross_certifying;
+
+    PacketProtocolLayer ppl;
+};
+
+static void ssh2_transport_free(PacketProtocolLayer *);
+static void ssh2_transport_process_queue(PacketProtocolLayer *);
+static int ssh2_transport_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+                                       SessionSpecialCode code, int arg);
+static int ssh2_transport_want_user_input(PacketProtocolLayer *ppl);
+static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl);
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static void ssh2_transport_dialog_callback(void *, int);
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
+static void ssh2_transport_higher_layer_packet_callback(void *context);
+
+static const struct PacketProtocolLayerVtable ssh2_transport_vtable = {
+    ssh2_transport_free,
+    ssh2_transport_process_queue,
+    ssh2_transport_get_specials,
+    ssh2_transport_special_cmd,
+    ssh2_transport_want_user_input,
+    ssh2_transport_got_user_input,
+    ssh2_transport_reconfigure,
+    NULL, /* no protocol name for this layer */
+};
+
+#ifndef NO_GSSAPI
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+                                      int definitely_rekeying);
+static void ssh_init_transient_hostkey_store(struct ssh2_transport_state *);
+static void ssh_cleanup_transient_hostkey_store(struct ssh2_transport_state *);
+static void ssh_store_transient_hostkey(
+    struct ssh2_transport_state *s, ssh_key *key);
+static int ssh_verify_transient_hostkey(
+    struct ssh2_transport_state *s, ssh_key *key);
+static int ssh_have_transient_hostkey(
+    struct ssh2_transport_state *s, const ssh_keyalg *alg);
+static int ssh_have_any_transient_hostkey(
+    struct ssh2_transport_state *s);
+#endif
+
+static int ssh2_transport_timer_update(struct ssh2_transport_state *s,
+                                       unsigned long rekey_time);
+
+static const char *const kexlist_descr[NKEXLIST] = {
+    "key exchange algorithm",
+    "host key algorithm",
+    "client-to-server cipher",
+    "server-to-client cipher",
+    "client-to-server MAC",
+    "server-to-client MAC",
+    "client-to-server compression method",
+    "server-to-client compression method"
+};
+
+PacketProtocolLayer *ssh2_transport_new(
+    Conf *conf, const char *host, int port, const char *fullhostname,
+    const char *client_greeting, const char *server_greeting,
+    struct ssh_connection_shared_gss_state *shgss,
+    struct DataTransferStats *stats,
+    PacketProtocolLayer *higher_layer)
+{
+    struct ssh2_transport_state *s = snew(struct ssh2_transport_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh2_transport_vtable;
+
+    s->conf = conf_copy(conf);
+    s->savedhost = dupstr(host);
+    s->savedport = port;
+    s->fullhostname = dupstr(fullhostname);
+    s->shgss = shgss;
+    s->client_greeting = dupstr(client_greeting);
+    s->server_greeting = dupstr(server_greeting);
+    s->stats = stats;
+
+    pq_in_init(&s->pq_in_higher);
+    pq_out_init(&s->pq_out_higher);
+    s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher;
+    s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback;
+    s->ic_pq_out_higher.ctx = &s->ppl;
+
+    s->higher_layer = higher_layer;
+    s->higher_layer->selfptr = &s->higher_layer;
+    ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher);
+
+#ifndef NO_GSSAPI
+    s->gss_cred_expiry = GSS_NO_EXPIRATION;
+    s->shgss->srv_name = GSS_C_NO_NAME;
+    s->shgss->ctx = NULL;
+    ssh_init_transient_hostkey_store(s);
+#endif
+    s->gss_kex_used = FALSE;
+
+    ssh2_transport_set_max_data_size(s);
+
+    return &s->ppl;
+}
+
+static void ssh2_transport_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    /*
+     * As our last act before being freed, move any outgoing packets
+     * off our higher layer's output queue on to our own output queue.
+     * We might be being freed while the SSH connection is still alive
+     * (because we're initiating shutdown from our end), in which case
+     * we don't want those last few packets to get lost.
+     *
+     * (If our owner were to have already destroyed our output pq
+     * before wanting to free us, then it would have to reset our
+     * publicly visible out_pq field to NULL to inhibit this attempt.
+     * But that's not how I expect the shutdown sequence to go in
+     * practice.)
+     */
+    if (s->ppl.out_pq)
+        pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+    conf_free(s->conf);
+
+    ssh_ppl_free(s->higher_layer);
+
+    pq_in_clear(&s->pq_in_higher);
+    pq_out_clear(&s->pq_out_higher);
+
+    sfree(s->savedhost);
+    sfree(s->fullhostname);
+    sfree(s->client_greeting);
+    sfree(s->server_greeting);
+    sfree(s->keystr);
+    sfree(s->hostkey_str);
+    sfree(s->fingerprint);
+    if (s->hkey) {
+        ssh_key_free(s->hkey);
+        s->hkey = NULL;
+    }
+    if (s->e) freebn(s->e);
+    if (s->f) freebn(s->f);
+    if (s->p) freebn(s->p);
+    if (s->g) freebn(s->g);
+    if (s->K) freebn(s->K);
+    if (s->dh_ctx)
+        dh_cleanup(s->dh_ctx);
+    if (s->rsa_kex_key)
+        ssh_rsakex_freekey(s->rsa_kex_key);
+    if (s->ecdh_key)
+        ssh_ecdhkex_freekey(s->ecdh_key);
+    if (s->exhash)
+        ssh_hash_free(s->exhash);
+#ifndef NO_GSSAPI
+    ssh_cleanup_transient_hostkey_store(s);
+#endif
+    sfree(s);
+}
+
+/*
+ * SSH-2 key derivation (RFC 4253 section 7.2).
+ */
+static void ssh2_mkkey(
+    struct ssh2_transport_state *s, strbuf *out,
+    Bignum K, unsigned char *H, char chr, int keylen)
+{
+    int hlen = s->kex_alg->hash->hlen;
+    int keylen_padded;
+    unsigned char *key;
+    ssh_hash *h;
+
+    if (keylen == 0)
+        return;
+
+    /*
+     * Round the requested amount of key material up to a multiple of
+     * the length of the hash we're using to make it. This makes life
+     * simpler because then we can just write each hash output block
+     * straight into the output buffer without fiddling about
+     * truncating the last one. Since it's going into a strbuf, and
+     * strbufs are always smemclr()ed on free, there's no need to
+     * worry about leaving extra potentially-sensitive data in memory
+     * that the caller didn't ask for.
+     */
+    keylen_padded = ((keylen + hlen - 1) / hlen) * hlen;
+
+    out->len = 0;
+    key = strbuf_append(out, keylen_padded);
+
+    /* First hlen bytes. */
+    h = ssh_hash_new(s->kex_alg->hash);
+    if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+        put_mp_ssh2(h, K);
+    put_data(h, H, hlen);
+    put_byte(h, chr);
+    put_data(h, s->session_id, s->session_id_len);
+    ssh_hash_final(h, key);
+
+    /* Subsequent blocks of hlen bytes. */
+    if (keylen_padded > hlen) {
+        int offset;
+
+        h = ssh_hash_new(s->kex_alg->hash);
+        if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+            put_mp_ssh2(h, K);
+        put_data(h, H, hlen);
+
+        for (offset = hlen; offset < keylen_padded; offset += hlen) {
+            put_data(h, key + offset - hlen, hlen);
+            ssh_hash *h2 = ssh_hash_copy(h);
+            ssh_hash_final(h2, key + offset);
+        }
+
+        ssh_hash_free(h);
+    }
+}
+
+/*
+ * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
+ * If the algorithm is already in the list, return a pointer to its
+ * entry, otherwise return an entry from the end of the list.
+ * This assumes that every time a particular name is passed in, it
+ * comes from the same string constant.  If this isn't true, this
+ * function may need to be rewritten to use strcmp() instead.
+ */
+static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm
+                                                     *list, const char *name)
+{
+    int i;
+
+    for (i = 0; i < MAXKEXLIST; i++)
+        if (list[i].name == NULL || list[i].name == name) {
+            list[i].name = name;
+            return &list[i];
+        }
+    assert(!"No space in KEXINIT list");
+    return NULL;
+}
+
+int ssh2_common_filter_queue(PacketProtocolLayer *ppl)
+{
+    static const char *const ssh2_disconnect_reasons[] = {
+        NULL,
+        "host not allowed to connect",
+        "protocol error",
+        "key exchange failed",
+        "host authentication failed",
+        "MAC error",
+        "compression error",
+        "service not available",
+        "protocol version not supported",
+        "host key not verifiable",
+        "connection lost",
+        "by application",
+        "too many connections",
+        "auth cancelled by user",
+        "no more auth methods available",
+        "illegal user name",
+    };
+
+    PktIn *pktin;
+    ptrlen msg;
+    int reason;
+
+    while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH2_MSG_DISCONNECT:
+            reason = get_uint32(pktin);
+            msg = get_string(pktin);
+
+            ssh_remote_error(
+                ppl->ssh, "Server sent disconnect message\n"
+                "type %d (%s):\n\"%.*s\"", reason,
+                ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+                 ssh2_disconnect_reasons[reason] : "unknown"),
+                PTRLEN_PRINTF(msg));
+            return TRUE;               /* indicate that we've been freed */
+
+          case SSH2_MSG_DEBUG:
+            /* XXX maybe we should actually take notice of the return value */
+            get_bool(pktin);
+            msg = get_string(pktin);
+            ppl_logevent(("Remote debug message: %.*s", PTRLEN_PRINTF(msg)));
+            pq_pop(ppl->in_pq);
+            break;
+
+          case SSH2_MSG_IGNORE:
+            /* Do nothing, because we're ignoring it! Duhh. */
+            pq_pop(ppl->in_pq);
+            break;
+
+          default:
+            return FALSE;
+        }
+    }
+
+    return FALSE;
+}
+
+static int ssh2_transport_filter_queue(struct ssh2_transport_state *s)
+{
+    PktIn *pktin;
+
+    while (1) {
+        if (ssh2_common_filter_queue(&s->ppl))
+            return TRUE;
+        if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+            return FALSE;
+
+        /* Pass on packets to the next layer if they're outside
+         * the range reserved for the transport protocol. */
+        if (pktin->type >= 50) {
+            /* ... except that we shouldn't tolerate higher-layer
+             * packets coming from the server before we've seen
+             * the first NEWKEYS. */
+            if (!s->higher_layer_ok) {
+                ssh_proto_error(s->ppl.ssh, "Received premature higher-"
+                                "layer packet, type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return TRUE;
+            }
+
+            pq_pop(s->ppl.in_pq);
+            pq_push(&s->pq_in_higher, pktin);
+        } else {
+            /* Anything else is a transport-layer packet that the main
+             * process_queue coroutine should handle. */
+            return FALSE;
+        }
+    }
+}
+
+static PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
+{
+    ssh2_transport_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+    PktIn *pktin;
+    PktOut *pktout;
+
+    /* Filter centrally handled messages off the front of the queue on
+     * every entry to this coroutine, no matter where we're resuming
+     * from, even if we're _not_ looping on pq_pop. That way we can
+     * still proactively handle those messages even if we're waiting
+     * for a user response. */
+    ssh2_transport_filter_queue(s);
+
+    crBegin(s->crState);
+
+    s->in.cipher = s->out.cipher = NULL;
+    s->in.mac = s->out.mac = NULL;
+    s->in.comp = s->out.comp = NULL;
+
+    s->got_session_id = FALSE;
+    s->userauth_succeeded = FALSE;
+    s->pending_compression = FALSE;
+    s->need_gss_transient_hostkey = FALSE;
+    s->warned_about_no_gss_transient_hostkey = FALSE;
+
+    /*
+     * Be prepared to work around the buggy MAC problem.
+     */
+    if (s->ppl.remote_bugs & BUG_SSH2_HMAC)
+        s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
+    else
+        s->maclist = macs, s->nmacs = lenof(macs);
+
+  begin_key_exchange:
+
+#ifndef NO_GSSAPI
+    if (s->need_gss_transient_hostkey) {
+        /*
+         * This flag indicates a special case in which we must not do
+         * GSS key exchange even if we could. (See comments below,
+         * where the flag was set on the previous key exchange.)
+         */
+        s->can_gssapi_keyex = FALSE;
+    } else if (conf_get_int(s->conf, CONF_try_gssapi_kex)) {
+        /*
+         * We always check if we have GSS creds before we come up with
+         * the kex algorithm list, otherwise future rekeys will fail
+         * when creds expire. To make this so, this code section must
+         * follow the begin_key_exchange label above, otherwise this
+         * section would execute just once per-connection.
+         *
+         * Update GSS state unless the reason we're here is that a
+         * timer just checked the GSS state and decided that we should
+         * rekey to update delegated credentials. In that case, the
+         * state is "fresh".
+         */
+        if (s->rekey_class != RK_GSS_UPDATE)
+            ssh2_transport_gss_update(s, TRUE);
+
+        /* Do GSSAPI KEX when capable */
+        s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE;
+
+        /*
+         * But not when failure is likely. [ GSS implementations may
+         * attempt (and fail) to use a ticket that is almost expired
+         * when retrieved from the ccache that actually expires by the
+         * time the server receives it. ]
+         *
+         * Note: The first time always try KEXGSS if we can, failures
+         * will be very rare, and disabling the initial GSS KEX is
+         * worse. Some day GSS libraries will ignore cached tickets
+         * whose lifetime is critically short, and will instead use
+         * fresh ones.
+         */
+        if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0)
+            s->can_gssapi_keyex = 0;
+        s->gss_delegate = conf_get_int(s->conf, CONF_gssapifwd);
+    } else {
+        s->can_gssapi_keyex = FALSE;
+    }
+#endif
+
+    s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX;
+    {
+        int i, j, k, warn;
+        struct kexinit_algorithm *alg;
+
+        /*
+         * Set up the preferred key exchange. (NULL => warn below here)
+         */
+        s->n_preferred_kex = 0;
+        if (s->can_gssapi_keyex)
+            s->preferred_kex[s->n_preferred_kex++] = &ssh_gssk5_sha1_kex;
+        for (i = 0; i < KEX_MAX; i++) {
+	    switch (conf_get_int_int(s->conf, CONF_ssh_kexlist, i)) {
+              case KEX_DHGEX:
+                s->preferred_kex[s->n_preferred_kex++] =
+                    &ssh_diffiehellman_gex;
+                break;
+              case KEX_DHGROUP14:
+                s->preferred_kex[s->n_preferred_kex++] =
+                    &ssh_diffiehellman_group14;
+                break;
+              case KEX_DHGROUP1:
+                s->preferred_kex[s->n_preferred_kex++] =
+                    &ssh_diffiehellman_group1;
+                break;
+              case KEX_RSA:
+                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. */
+                if (i < KEX_MAX - 1) {
+                    s->preferred_kex[s->n_preferred_kex++] = NULL;
+                }
+                break;
+            }
+        }
+
+        /*
+         * 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(s->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)
+         */
+        s->n_preferred_ciphers = 0;
+	for (i = 0; i < CIPHER_MAX; i++) {
+	    switch (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i)) {
+              case CIPHER_BLOWFISH:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
+                break;
+              case CIPHER_DES:
+		if (conf_get_int(s->conf, CONF_ssh2_des_cbc))
+                    s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des;
+                break;
+              case CIPHER_3DES:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des;
+                break;
+              case CIPHER_AES:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
+                break;
+              case CIPHER_ARCFOUR:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
+                break;
+              case CIPHER_CHACHA20:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp;
+                break;
+              case CIPHER_WARN:
+                /* Flag for later. Don't bother if it's the last in
+                 * the list. */
+                if (i < CIPHER_MAX - 1) {
+                    s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
+                }
+                break;
+            }
+        }
+
+        /*
+         * Set up preferred compression.
+         */
+	if (conf_get_int(s->conf, CONF_compression))
+            s->preferred_comp = &ssh_zlib;
+        else
+            s->preferred_comp = &ssh_comp_none;
+
+        /*
+         * Flag that KEX is in progress.
+         */
+        s->kex_in_progress = TRUE;
+
+        for (i = 0; i < NKEXLIST; i++)
+            for (j = 0; j < MAXKEXLIST; j++)
+                s->kexlists[i][j].name = NULL;
+        /* List key exchange algorithms. */
+        warn = FALSE;
+        for (i = 0; i < s->n_preferred_kex; i++) {
+            const struct ssh_kexes *k = s->preferred_kex[i];
+            if (!k) warn = TRUE;
+            else for (j = 0; j < k->nkexes; j++) {
+                    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX],
+                                              k->list[j]->name);
+                    alg->u.kex.kex = k->list[j];
+                    alg->u.kex.warn = warn;
+                }
+        }
+        /* List server host key algorithms. */
+        if (!s->got_session_id) {
+            /*
+             * 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.
+             */
+            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(s->savedhost, s->savedport,
+                                          hostkey_algs[j].alg->cache_id)) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                                  hostkey_algs[j].alg->ssh_id);
+                        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->ssh_id);
+                    alg->u.hk.hostkey = hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
+#ifndef NO_GSSAPI
+        } else if (s->gss_kex_used && !s->need_gss_transient_hostkey) {
+            /*
+             * If we've previously done a GSSAPI KEX, then we list
+             * precisely the algorithms for which a previous GSS key
+             * exchange has delivered us a host key, because we expect
+             * one of exactly those keys to be used in any subsequent
+             * non-GSS-based rekey.
+             *
+             * An exception is if this is the key exchange we
+             * triggered for the purposes of populating that cache -
+             * in which case the cache will currently be empty, which
+             * isn't helpful!
+             */
+            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 (ssh_have_transient_hostkey(s, hostkey_algs[j].alg)) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                                  hostkey_algs[j].alg->ssh_id);
+                        alg->u.hk.hostkey = hostkey_algs[j].alg;
+                        alg->u.hk.warn = warn;
+                    }
+                }
+            }
+#endif
+        } 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(s->kex_alg);
+            alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                      s->hostkey_alg->ssh_id);
+            alg->u.hk.hostkey = s->hostkey_alg;
+            alg->u.hk.warn = FALSE;
+        }
+        if (s->can_gssapi_keyex) {
+            alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], "null");
+            alg->u.hk.hostkey = NULL;
+        }
+        /* List encryption algorithms (client->server then server->client). */
+        for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
+            warn = FALSE;
+#ifdef FUZZING
+            alg = ssh2_kexinit_addalg(s->kexlists[k], "none");
+            alg->u.cipher.cipher = NULL;
+            alg->u.cipher.warn = warn;
+#endif /* FUZZING */
+            for (i = 0; i < s->n_preferred_ciphers; i++) {
+                const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+                if (!c) warn = TRUE;
+                else for (j = 0; j < c->nciphers; j++) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[k],
+                                                  c->list[j]->name);
+                        alg->u.cipher.cipher = c->list[j];
+                        alg->u.cipher.warn = warn;
+                    }
+            }
+        }
+        /* List MAC algorithms (client->server then server->client). */
+        for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+            alg = ssh2_kexinit_addalg(s->kexlists[j], "none");
+            alg->u.mac.mac = NULL;
+            alg->u.mac.etm = FALSE;
+#endif /* FUZZING */
+            for (i = 0; i < s->nmacs; i++) {
+                alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
+                alg->u.mac.mac = s->maclist[i];
+                alg->u.mac.etm = FALSE;
+            }
+            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) {
+                    alg = ssh2_kexinit_addalg(s->kexlists[j],
+                                              s->maclist[i]->etm_name);
+                    alg->u.mac.mac = s->maclist[i];
+                    alg->u.mac.etm = TRUE;
+                }
+        }
+        /* List client->server compression algorithms,
+         * then server->client compression algorithms. (We use the
+         * same set twice.) */
+        for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
+            assert(lenof(compressions) > 1);
+            /* Prefer non-delayed versions */
+            alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name);
+            alg->u.comp = s->preferred_comp;
+            /* 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) {
+                alg = ssh2_kexinit_addalg(s->kexlists[j],
+                                          s->preferred_comp->delayed_name);
+                alg->u.comp = s->preferred_comp;
+            }
+            for (i = 0; i < lenof(compressions); i++) {
+                const struct ssh_compression_alg *c = compressions[i];
+                alg = ssh2_kexinit_addalg(s->kexlists[j], c->name);
+                alg->u.comp = c;
+                if (s->userauth_succeeded && c->delayed_name) {
+                    alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name);
+                    alg->u.comp = c;
+                }
+            }
+        }
+        /*
+         * Construct and send our key exchange packet.
+         */
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+        for (i = 0; i < 16; i++)
+            put_byte(pktout, (unsigned char) random_byte());
+        for (i = 0; i < NKEXLIST; i++) {
+            strbuf *list = strbuf_new();
+            for (j = 0; j < MAXKEXLIST; j++) {
+                if (s->kexlists[i][j].name == NULL) break;
+                add_to_commasep(list, s->kexlists[i][j].name);
+            }
+            put_stringsb(pktout, list);
+        }
+        /* List client->server languages. Empty list. */
+        put_stringz(pktout, "");
+        /* List server->client languages. Empty list. */
+        put_stringz(pktout, "");
+        /* First KEX packet does _not_ follow, because we're not that brave. */
+        put_bool(pktout, FALSE);
+        /* Reserved. */
+        put_uint32(pktout, 0);
+    }
+
+    s->our_kexinitlen = pktout->length - 5;
+    s->our_kexinit = snewn(s->our_kexinitlen, unsigned char);
+    memcpy(s->our_kexinit, pktout->data + 5, s->our_kexinitlen);
+
+    pq_push(s->ppl.out_pq, pktout);
+
+    crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+
+    /*
+     * Now examine the other side's KEXINIT to see what we're up
+     * to.
+     */
+    {
+        ptrlen str;
+        int i, j;
+
+        if (pktin->type != SSH2_MSG_KEXINIT) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting KEXINIT, type %d (%s)", pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx, pktin->type));
+            return;
+        }
+        s->kex_alg = NULL;
+        s->hostkey_alg = NULL;
+        s->in.cipher = s->out.cipher = NULL;
+        s->in.mac = s->out.mac = NULL;
+        s->in.comp = s->out.comp = NULL;
+        s->warn_kex = s->warn_hk = FALSE;
+        s->warn_cscipher = s->warn_sccipher = FALSE;
+
+        get_data(pktin, 16);           /* skip garbage cookie */
+
+        s->guessok = FALSE;
+        for (i = 0; i < NKEXLIST; i++) {
+            str = get_string(pktin);
+            if (get_err(pktin)) {
+                ssh_proto_error(s->ppl.ssh, "KEXINIT packet was incomplete");
+                return;
+            }
+
+            /* If we've already selected a cipher which requires a
+             * particular MAC, then just select that, and don't even
+             * bother looking through the server's KEXINIT string for
+             * MACs. */
+            if (i == KEXLIST_CSMAC && s->out.cipher &&
+                s->out.cipher->required_mac) {
+                s->out.mac = s->out.cipher->required_mac;
+                s->out.etm_mode = !!(s->out.mac->etm_name);
+                goto matched;
+            }
+            if (i == KEXLIST_SCMAC && s->in.cipher &&
+                s->in.cipher->required_mac) {
+                s->in.mac = s->in.cipher->required_mac;
+                s->in.etm_mode = !!(s->in.mac->etm_name);
+                goto matched;
+            }
+
+            for (j = 0; j < MAXKEXLIST; j++) {
+                struct kexinit_algorithm *alg = &s->kexlists[i][j];
+                if (alg->name == NULL) break;
+                if (in_commasep_string(alg->name, str.ptr, str.len)) {
+                    /* We've found a matching algorithm. */
+                    if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) {
+                        /* Check if we might need to ignore first kex pkt */
+                        if (j != 0 ||
+                            !first_in_commasep_string(alg->name,
+                                                      str.ptr, str.len))
+                            s->guessok = FALSE;
+                    }
+                    if (i == KEXLIST_KEX) {
+                        s->kex_alg = alg->u.kex.kex;
+                        s->warn_kex = alg->u.kex.warn;
+                    } else if (i == KEXLIST_HOSTKEY) {
+                        /*
+                         * Ignore an unexpected/inappropriate offer of "null",
+                         * we offer "null" when we're willing to use GSS KEX,
+                         * but it is only acceptable when GSSKEX is actually
+                         * selected.
+                         */
+                        if (alg->u.hk.hostkey == NULL &&
+                            s->kex_alg->main_type != KEXTYPE_GSS)
+                            continue;
+                        s->hostkey_alg = alg->u.hk.hostkey;
+                        s->warn_hk = alg->u.hk.warn;
+                    } else if (i == KEXLIST_CSCIPHER) {
+                        s->out.cipher = alg->u.cipher.cipher;
+                        s->warn_cscipher = alg->u.cipher.warn;
+                    } else if (i == KEXLIST_SCCIPHER) {
+                        s->in.cipher = alg->u.cipher.cipher;
+                        s->warn_sccipher = alg->u.cipher.warn;
+                    } else if (i == KEXLIST_CSMAC) {
+                        s->out.mac = alg->u.mac.mac;
+                        s->out.etm_mode = alg->u.mac.etm;
+                    } else if (i == KEXLIST_SCMAC) {
+                        s->in.mac = alg->u.mac.mac;
+                        s->in.etm_mode = alg->u.mac.etm;
+                    } else if (i == KEXLIST_CSCOMP) {
+                        s->out.comp = alg->u.comp;
+                    } else if (i == KEXLIST_SCCOMP) {
+                        s->in.comp = alg->u.comp;
+                    }
+                    goto matched;
+                }
+
+                /* Set a flag if there's a delayed compression option
+                 * available for a compression method that we just
+                 * failed to select the immediate version of. */
+                s->pending_compression = (
+                    (i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) &&
+                    in_commasep_string(alg->u.comp->delayed_name,
+                                       str.ptr, str.len) &&
+                    !s->userauth_succeeded);
+            }
+            ssh_sw_abort(s->ppl.ssh, "Couldn't agree a %s (available: %.*s)",
+                         kexlist_descr[i], PTRLEN_PRINTF(str));
+            return;
+          matched:;
+
+            if (i == KEXLIST_HOSTKEY &&
+                !s->gss_kex_used &&
+                s->kex_alg->main_type != KEXTYPE_GSS) {
+                int j;
+
+                /*
+                 * In addition to deciding which host key we're
+                 * actually going to use, we should make a list of the
+                 * 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.
+                 */
+                s->n_uncert_hostkeys = 0;
+
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].alg != s->hostkey_alg &&
+                        in_commasep_string(hostkey_algs[j].alg->ssh_id,
+                                           str.ptr, str.len) &&
+                        !have_ssh_host_key(s->savedhost, s->savedport,
+                                           hostkey_algs[j].alg->cache_id)) {
+                        s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
+                    }
+                }
+            }
+        }
+
+        if (s->pending_compression) {
+            ppl_logevent(("Server supports delayed compression; "
+                          "will try this later"));
+        }
+        get_string(pktin);  /* client->server language */
+        get_string(pktin);  /* server->client language */
+        s->ignorepkt = get_bool(pktin) && !s->guessok;
+
+        s->exhash = ssh_hash_new(s->kex_alg->hash);
+        put_stringz(s->exhash, s->client_greeting);
+        put_stringz(s->exhash, s->server_greeting);
+        put_string(s->exhash, s->our_kexinit, s->our_kexinitlen);
+        sfree(s->our_kexinit);
+        /* Include the type byte in the hash of server's KEXINIT */
+        put_string(s->exhash,
+                   (const char *)BinarySource_UPCAST(pktin)->data - 1,
+                   BinarySource_UPCAST(pktin)->len + 1);
+
+        if (s->warn_kex) {
+            s->dlgret = askalg(s->ppl.frontend, "key-exchange algorithm",
+                               s->kex_alg->name,
+                               ssh2_transport_dialog_callback, s);
+            crMaybeWaitUntilV(s->dlgret >= 0);
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh, "User aborted at kex warning");
+                return;
+            }
+        }
+
+        if (s->warn_hk) {
+            int j, k;
+            char *betteralgs;
+
+            /*
+             * 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 < s->n_uncert_hostkeys; j++) {
+                const struct ssh_signkey_with_user_pref_id *hktype =
+                    &hostkey_algs[s->uncert_hostkeys[j]];
+                int better = FALSE;
+                for (k = 0; k < HK_MAX; k++) {
+                    int id = conf_get_int_int(s->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->ssh_id,
+                                            (const char *)NULL);
+                        sfree(old_ba);
+                    } else {
+                        betteralgs = dupstr(hktype->alg->ssh_id);
+                    }
+                }
+            }
+            if (betteralgs) {
+                s->dlgret = askhk(
+                    s->ppl.frontend, s->hostkey_alg->ssh_id, betteralgs,
+                    ssh2_transport_dialog_callback, s);
+                sfree(betteralgs);
+            } else {
+                s->dlgret = askalg(s->ppl.frontend, "host key type",
+                                   s->hostkey_alg->ssh_id,
+                                   ssh2_transport_dialog_callback, s);
+            }
+            crMaybeWaitUntilV(s->dlgret >= 0);
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh, "User aborted at host key warning");
+                return;
+            }
+        }
+
+        if (s->warn_cscipher) {
+            s->dlgret = askalg(s->ppl.frontend,
+                               "client-to-server cipher",
+                               s->out.cipher->name,
+                               ssh2_transport_dialog_callback, s);
+            crMaybeWaitUntilV(s->dlgret >= 0);
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
+                return;
+            }
+        }
+
+        if (s->warn_sccipher) {
+            s->dlgret = askalg(s->ppl.frontend,
+                               "server-to-client cipher",
+                               s->in.cipher->name,
+                               ssh2_transport_dialog_callback, s);
+            crMaybeWaitUntilV(s->dlgret >= 0);
+            if (s->dlgret == 0) {
+                ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
+                return;
+            }
+        }
+
+        if (s->ignorepkt) /* first_kex_packet_follows */
+            crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+    }
+
+    if (s->kex_alg->main_type == KEXTYPE_DH) {
+        /*
+         * Work out the number of bits of key we will need from the
+         * key exchange. We start with the maximum key length of
+         * either cipher...
+         */
+        {
+            int csbits, scbits;
+
+            csbits = s->out.cipher ? s->out.cipher->real_keybits : 0;
+            scbits = s->in.cipher ? s->in.cipher->real_keybits : 0;
+            s->nbits = (csbits > scbits ? csbits : scbits);
+        }
+        /* The keys only have hlen-bit entropy, since they're based on
+         * a hash. So cap the key size at hlen bits. */
+        if (s->nbits > s->kex_alg->hash->hlen * 8)
+            s->nbits = s->kex_alg->hash->hlen * 8;
+
+        /*
+         * If we're doing Diffie-Hellman group exchange, start by
+         * requesting a group.
+         */
+        if (dh_is_gex(s->kex_alg)) {
+            ppl_logevent(("Doing Diffie-Hellman group exchange"));
+            s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
+            /*
+             * Work out how big a DH group we will need to allow that
+             * much data.
+             */
+            s->pbits = 512 << ((s->nbits - 1) / 64);
+            if (s->pbits < DH_MIN_SIZE)
+                s->pbits = DH_MIN_SIZE;
+            if (s->pbits > DH_MAX_SIZE)
+                s->pbits = DH_MAX_SIZE;
+            if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
+                put_uint32(pktout, s->pbits);
+            } else {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST);
+                put_uint32(pktout, DH_MIN_SIZE);
+                put_uint32(pktout, s->pbits);
+                put_uint32(pktout, DH_MAX_SIZE);
+            }
+            pq_push(s->ppl.out_pq, pktout);
+
+            crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                                "expecting Diffie-Hellman group, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+            s->p = get_mp_ssh2(pktin);
+            s->g = get_mp_ssh2(pktin);
+            if (get_err(pktin)) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Unable to parse Diffie-Hellman group packet");
+                return;
+            }
+            s->dh_ctx = dh_setup_gex(s->p, s->g);
+            s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+            s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+        } else {
+            s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
+            s->dh_ctx = dh_setup_group(s->kex_alg);
+            s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+            s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+            ppl_logevent(("Using Diffie-Hellman with standard group \"%s\"",
+                          s->kex_alg->groupname));
+        }
+
+        ppl_logevent(("Doing Diffie-Hellman key exchange with hash %s",
+                      s->kex_alg->hash->text_name));
+        /*
+         * Now generate and send e for Diffie-Hellman.
+         */
+        set_busy_status(s->ppl.frontend, BUSY_CPU); /* this can take a while */
+        s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value);
+        put_mp_ssh2(pktout, s->e);
+        pq_push(s->ppl.out_pq, pktout);
+
+        set_busy_status(s->ppl.frontend, BUSY_WAITING); /* wait for server */
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != s->kex_reply_value) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting Diffie-Hellman reply, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+        }
+        set_busy_status(s->ppl.frontend, BUSY_CPU); /* cogitate */
+        s->hostkeydata = get_string(pktin);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+        s->f = get_mp_ssh2(pktin);
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh,
+                            "Unable to parse Diffie-Hellman reply packet");
+            return;
+        }
+
+        {
+            const char *err = dh_validate_f(s->dh_ctx, s->f);
+            if (err) {
+                ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed "
+                                "validation: %s", err);
+                return;
+            }
+        }
+        s->K = dh_find_K(s->dh_ctx, s->f);
+
+        /* We assume everything from now on will be quick, and it might
+         * involve user interaction. */
+        set_busy_status(s->ppl.frontend, BUSY_NOT);
+
+        put_stringpl(s->exhash, s->hostkeydata);
+        if (dh_is_gex(s->kex_alg)) {
+            if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+                put_uint32(s->exhash, DH_MIN_SIZE);
+            put_uint32(s->exhash, s->pbits);
+            if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+                put_uint32(s->exhash, DH_MAX_SIZE);
+            put_mp_ssh2(s->exhash, s->p);
+            put_mp_ssh2(s->exhash, s->g);
+        }
+        put_mp_ssh2(s->exhash, s->e);
+        put_mp_ssh2(s->exhash, s->f);
+
+        dh_cleanup(s->dh_ctx);
+        s->dh_ctx = NULL;
+        freebn(s->f); s->f = NULL;
+        freebn(s->e); s->e = NULL;
+        if (dh_is_gex(s->kex_alg)) {
+            freebn(s->g); s->g = NULL;
+            freebn(s->p); s->p = NULL;
+        }
+    } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
+
+        ppl_logevent(("Doing ECDH key exchange with curve %s and hash %s",
+                      ssh_ecdhkex_curve_textname(s->kex_alg),
+                      s->kex_alg->hash->text_name));
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
+
+        s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg);
+        if (!s->ecdh_key) {
+            ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
+            return;
+        }
+
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
+        {
+            strbuf *pubpoint = strbuf_new();
+            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            put_stringsb(pktout, pubpoint);
+        }
+
+        pq_push(s->ppl.out_pq, pktout);
+
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting ECDH reply, type %d (%s)", pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+        }
+
+        s->hostkeydata = get_string(pktin);
+        put_stringpl(s->exhash, s->hostkeydata);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+        {
+            strbuf *pubpoint = strbuf_new();
+            ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+            put_string(s->exhash, pubpoint->u, pubpoint->len);
+            strbuf_free(pubpoint);
+        }
+
+        {
+            ptrlen keydata = get_string(pktin);
+            put_stringpl(s->exhash, keydata);
+            s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata.ptr, keydata.len);
+            if (!get_err(pktin) && !s->K) {
+                ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+                                "point in ECDH reply");
+                return;
+            }
+        }
+
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet");
+            return;
+        }
+
+        ssh_ecdhkex_freekey(s->ecdh_key);
+        s->ecdh_key = NULL;
+#ifndef NO_GSSAPI
+    } else if (s->kex_alg->main_type == KEXTYPE_GSS) {
+        ptrlen data;
+
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
+        s->init_token_sent = 0;
+        s->complete_rcvd = 0;
+        s->hkey = NULL;
+        s->fingerprint = NULL;
+        s->keystr = NULL;
+
+        /*
+         * Work out the number of bits of key we will need from the
+         * key exchange. We start with the maximum key length of
+         * either cipher...
+         *
+         * This is rote from the KEXTYPE_DH section above.
+         */
+        {
+            int csbits, scbits;
+
+            csbits = s->out.cipher->real_keybits;
+            scbits = s->in.cipher->real_keybits;
+            s->nbits = (csbits > scbits ? csbits : scbits);
+        }
+        /* The keys only have hlen-bit entropy, since they're based on
+         * a hash. So cap the key size at hlen bits. */
+        if (s->nbits > s->kex_alg->hash->hlen * 8)
+            s->nbits = s->kex_alg->hash->hlen * 8;
+
+        if (dh_is_gex(s->kex_alg)) {
+            /*
+             * Work out how big a DH group we will need to allow that
+             * much data.
+             */
+            s->pbits = 512 << ((s->nbits - 1) / 64);
+            ppl_logevent(("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
+                          "group exchange, with minimum %d bits", s->pbits));
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
+            put_uint32(pktout, s->pbits); /* min */
+            put_uint32(pktout, s->pbits); /* preferred */
+            put_uint32(pktout, s->pbits * 2); /* max */
+            pq_push(s->ppl.out_pq, pktout);
+
+            crMaybeWaitUntilV(
+                (pktin = ssh2_transport_pop(s)) != NULL);
+            if (pktin->type != SSH2_MSG_KEXGSS_GROUP) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                                "expecting Diffie-Hellman group, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+            s->p = get_mp_ssh2(pktin);
+            s->g = get_mp_ssh2(pktin);
+            if (get_err(pktin)) {
+                ssh_proto_error(s->ppl.ssh,
+                                "Unable to parse Diffie-Hellman group packet");
+                return;
+            }
+            s->dh_ctx = dh_setup_gex(s->p, s->g);
+        } else {
+            s->dh_ctx = dh_setup_group(s->kex_alg);
+            ppl_logevent(("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
+                          " standard group \"%s\"", s->kex_alg->groupname));
+        }
+
+        ppl_logevent(("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key "
+                      "exchange with hash %s", s->kex_alg->hash->text_name));
+        /* Now generate e for Diffie-Hellman. */
+        set_busy_status(s->ppl.frontend, BUSY_CPU); /* this can take a while */
+        s->e = dh_create_e(s->dh_ctx, s->nbits * 2);
+
+        if (s->shgss->lib->gsslogmsg)
+            ppl_logevent(("%s", s->shgss->lib->gsslogmsg));
+
+        /* initial tokens are empty */
+        SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+        SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+        SSH_GSS_CLEAR_BUF(&s->mic);
+        s->gss_stat = s->shgss->lib->acquire_cred(
+            s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry);
+        if (s->gss_stat != SSH_GSS_OK) {
+            ssh_sw_abort(s->ppl.ssh,
+                         "GSSAPI key exchange failed to initialise");
+            return;
+        }
+
+        /* now enter the loop */
+        assert(s->shgss->srv_name);
+        do {
+            /*
+             * When acquire_cred yields no useful expiration, go with the
+             * service ticket expiration.
+             */
+            s->gss_stat = s->shgss->lib->init_sec_context(
+                s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name,
+                s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok,
+                (s->gss_cred_expiry == GSS_NO_EXPIRATION ?
+                 &s->gss_cred_expiry : NULL), NULL);
+            SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+
+            if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+                break; /* MIC is verified after the loop */
+
+            if (s->gss_stat != SSH_GSS_S_COMPLETE &&
+                s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+                if (s->shgss->lib->display_status(
+                        s->shgss->lib, s->shgss->ctx,
+                        &s->gss_buf) == SSH_GSS_OK) {
+                    char *err = s->gss_buf.value;
+                    ssh_sw_abort(s->ppl.ssh,
+                                 "GSSAPI key exchange failed to initialise "
+                                 "context: %s", err);
+                    sfree(err);
+                    return;
+                }
+            }
+            assert(s->gss_stat == SSH_GSS_S_COMPLETE ||
+                   s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+            if (!s->init_token_sent) {
+                s->init_token_sent = 1;
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp,
+                                            SSH2_MSG_KEXGSS_INIT);
+                if (s->gss_sndtok.length == 0) {
+                    ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: "
+                                 "no initial context token");
+                    return;
+                }
+                put_string(pktout,
+                           s->gss_sndtok.value, s->gss_sndtok.length);
+                put_mp_ssh2(pktout, s->e);
+                pq_push(s->ppl.out_pq, pktout);
+                s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+                ppl_logevent(("GSSAPI key exchange initialised"));
+            } else if (s->gss_sndtok.length != 0) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE);
+                put_string(pktout,
+                           s->gss_sndtok.value, s->gss_sndtok.length);
+                pq_push(s->ppl.out_pq, pktout);
+                s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+            }
+
+            if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+                break;
+
+          wait_for_gss_token:
+            crMaybeWaitUntilV(
+                (pktin = ssh2_transport_pop(s)) != NULL);
+            switch (pktin->type) {
+              case SSH2_MSG_KEXGSS_CONTINUE:
+                data = get_string(pktin);
+                s->gss_rcvtok.value = (char *)data.ptr;
+                s->gss_rcvtok.length = data.len;
+                continue;
+              case SSH2_MSG_KEXGSS_COMPLETE:
+                s->complete_rcvd = 1;
+                s->f = get_mp_ssh2(pktin);
+                data = get_string(pktin);
+                s->mic.value = (char *)data.ptr;
+                s->mic.length = data.len;
+                /* Save expiration time of cred when delegating */
+                if (s->gss_delegate && s->gss_cred_expiry != GSS_NO_EXPIRATION)
+                    s->gss_cred_expiry = s->gss_cred_expiry;
+                /* If there's a final token we loop to consume it */
+                if (get_bool(pktin)) {
+                    data = get_string(pktin);
+                    s->gss_rcvtok.value = (char *)data.ptr;
+                    s->gss_rcvtok.length = data.len;
+                    continue;
+                }
+                break;
+              case SSH2_MSG_KEXGSS_HOSTKEY:
+                s->hostkeydata = get_string(pktin);
+                if (s->hostkey_alg) {
+                    s->hkey = ssh_key_new_pub(s->hostkey_alg,
+                                              s->hostkeydata);
+                    put_string(s->exhash,
+                               s->hostkeydata.ptr, s->hostkeydata.len);
+                }
+                /*
+                 * Can't loop as we have no token to pass to
+                 * init_sec_context.
+                 */
+                goto wait_for_gss_token;
+              case SSH2_MSG_KEXGSS_ERROR:
+                /*
+                 * We have no use for the server's major and minor
+                 * status.  The minor status is really only
+                 * meaningful to the server, and with luck the major
+                 * status means something to us (but not really all
+                 * that much).  The string is more meaningful, and
+                 * hopefully the server sends any error tokens, as
+                 * that will produce the most useful information for
+                 * us.
+                 */
+                get_uint32(pktin); /* server's major status */
+                get_uint32(pktin); /* server's minor status */
+                data = get_string(pktin);
+                ppl_logevent(("GSSAPI key exchange failed; "
+                              "server's message: %.*s", PTRLEN_PRINTF(data)));
+                /* Language tag, but we have no use for it */
+                get_string(pktin);
+                /*
+                 * Wait for an error token, if there is one, or the
+                 * server's disconnect.  The error token, if there
+                 * is one, must follow the SSH2_MSG_KEXGSS_ERROR
+                 * message, per the RFC.
+                 */
+                goto wait_for_gss_token;
+              default:
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+                                "during GSSAPI key exchange, type %d (%s)",
+                                pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+        } while (s->gss_rcvtok.length ||
+                 s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
+                 !s->complete_rcvd);
+
+        s->K = dh_find_K(s->dh_ctx, s->f);
+
+        /* We assume everything from now on will be quick, and it might
+         * involve user interaction. */
+        set_busy_status(s->ppl.frontend, BUSY_NOT);
+
+        if (!s->hkey)
+            put_stringz(s->exhash, "");
+        if (dh_is_gex(s->kex_alg)) {
+            /* min,  preferred, max */
+            put_uint32(s->exhash, s->pbits);
+            put_uint32(s->exhash, s->pbits);
+            put_uint32(s->exhash, s->pbits * 2);
+
+            put_mp_ssh2(s->exhash, s->p);
+            put_mp_ssh2(s->exhash, s->g);
+        }
+        put_mp_ssh2(s->exhash, s->e);
+        put_mp_ssh2(s->exhash, s->f);
+
+        /*
+         * MIC verification is done below, after we compute the hash
+         * used as the MIC input.
+         */
+
+        dh_cleanup(s->dh_ctx);
+        s->dh_ctx = NULL;
+        freebn(s->f); s->f = NULL;
+        freebn(s->e); s->e = NULL;
+        if (dh_is_gex(s->kex_alg)) {
+            freebn(s->g); s->g = NULL;
+            freebn(s->p); s->p = NULL;
+        }
+#endif
+    } else {
+        ptrlen rsakeydata;
+
+        assert(s->kex_alg->main_type == KEXTYPE_RSA);
+        ppl_logevent(("Doing RSA key exchange with hash %s",
+                      s->kex_alg->hash->text_name));
+        s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
+        /*
+         * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+         * from the server.
+         */
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting RSA public key, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+        }
+
+        s->hostkeydata = get_string(pktin);
+        put_stringpl(s->exhash, s->hostkeydata);
+        s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+        rsakeydata = get_string(pktin);
+
+        s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata.ptr, rsakeydata.len);
+        if (!s->rsa_kex_key) {
+            ssh_proto_error(s->ppl.ssh,
+                            "Unable to parse RSA public key packet");
+            return;
+        }
+
+        put_stringpl(s->exhash, rsakeydata);
+
+        /*
+         * Next, set up a shared secret K, of precisely KLEN -
+         * 2*HLEN - 49 bits, where KLEN is the bit length of the
+         * RSA key modulus and HLEN is the bit length of the hash
+         * we're using.
+         */
+        {
+            int klen = ssh_rsakex_klen(s->rsa_kex_key);
+            int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49);
+            int i, byte = 0;
+            strbuf *buf;
+            unsigned char *outstr;
+            int outstrlen;
+
+            s->K = bn_power_2(nbits - 1);
+
+            for (i = 0; i < nbits; i++) {
+                if ((i & 7) == 0) {
+                    byte = random_byte();
+                }
+                bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1);
+            }
+
+            /*
+             * Encode this as an mpint.
+             */
+            buf = strbuf_new();
+            put_mp_ssh2(buf, s->K);
+
+            /*
+             * Encrypt it with the given RSA key.
+             */
+            outstrlen = (klen + 7) / 8;
+            outstr = snewn(outstrlen, unsigned char);
+            ssh_rsakex_encrypt(s->kex_alg->hash, buf->u, buf->len,
+                               outstr, outstrlen, s->rsa_kex_key);
+
+            /*
+             * And send it off in a return packet.
+             */
+            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET);
+            put_string(pktout, outstr, outstrlen);
+            pq_push(s->ppl.out_pq, pktout);
+
+            put_string(s->exhash, outstr, outstrlen);
+
+            strbuf_free(buf);
+            sfree(outstr);
+        }
+
+        ssh_rsakex_freekey(s->rsa_kex_key);
+        s->rsa_kex_key = NULL;
+
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+            ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                            "expecting RSA kex signature, type %d (%s)",
+                            pktin->type,
+                            ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                          s->ppl.bpp->pls->actx,
+                                          pktin->type));
+            return;
+        }
+
+        s->sigdata = get_string(pktin);
+        if (get_err(pktin)) {
+            ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature");
+            return;
+        }
+    }
+
+    put_mp_ssh2(s->exhash, s->K);
+    assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
+    ssh_hash_final(s->exhash, s->exchange_hash);
+    s->exhash = NULL;
+
+#ifndef NO_GSSAPI
+    if (s->kex_alg->main_type == KEXTYPE_GSS) {
+        Ssh_gss_buf gss_buf;
+        SSH_GSS_CLEAR_BUF(&s->gss_buf);
+
+        gss_buf.value = s->exchange_hash;
+        gss_buf.length = s->kex_alg->hash->hlen;
+        s->gss_stat = s->shgss->lib->verify_mic(
+            s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic);
+        if (s->gss_stat != SSH_GSS_OK) {
+            if (s->shgss->lib->display_status(
+                    s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
+                char *err = s->gss_buf.value;
+                ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+                             "not valid: %s", err);
+                sfree(err);
+            } else {
+                ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+                             "not valid");
+            }
+            return;
+        }
+
+        s->gss_kex_used = TRUE;
+
+        /*-
+         * If this the first KEX, save the GSS context for "gssapi-keyex"
+         * authentication.
+         *
+         * http://tools.ietf.org/html/rfc4462#section-4
+         *
+         * This method may be used only if the initial key exchange was
+         * performed using a GSS-API-based key exchange method defined in
+         * accordance with Section 2.  The GSS-API context used with this
+         * method is always that established during an initial GSS-API-based
+         * key exchange.  Any context established during key exchange for the
+         * purpose of rekeying MUST NOT be used with this method.
+         */
+        if (s->got_session_id) {
+            s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+        }
+        ppl_logevent(("GSSAPI Key Exchange complete!"));
+    }
+#endif
+
+    s->dh_ctx = NULL;
+
+#if 0
+    debug(("Exchange hash is:\n"));
+    dmemdump(s->exchange_hash, s->kex_alg->hash->hlen);
+#endif
+
+    /* In GSS keyex there's no hostkey signature to verify */
+    if (s->kex_alg->main_type != KEXTYPE_GSS) {
+        if (!s->hkey) {
+            ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
+            return;
+        }
+
+        if (!ssh_key_verify(
+                s->hkey, s->sigdata,
+                make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) {
+#ifndef FUZZING
+            ssh_proto_error(s->ppl.ssh, "Signature from server's host key "
+                            "is invalid");
+            return;
+#endif
+        }
+    }
+
+    s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL);
+#ifndef NO_GSSAPI
+    if (s->gss_kex_used) {
+        /*
+         * In a GSS-based session, check the host key (if any) against
+         * the transient host key cache. See comment above, at the
+         * definition of ssh_transient_hostkey_cache_entry.
+         */
+        if (s->kex_alg->main_type == KEXTYPE_GSS) {
+
+            /*
+             * We've just done a GSS key exchange. If it gave us a
+             * host key, store it.
+             */
+            if (s->hkey) {
+                s->fingerprint = ssh2_fingerprint(s->hkey);
+                ppl_logevent(("GSS kex provided fallback host key:"));
+                ppl_logevent(("%s", s->fingerprint));
+                sfree(s->fingerprint);
+                s->fingerprint = NULL;
+                ssh_store_transient_hostkey(s, s->hkey);
+            } else if (!ssh_have_any_transient_hostkey(s)) {
+                /*
+                 * But if it didn't, then we currently have no
+                 * fallback host key to use in subsequent non-GSS
+                 * rekeys. So we should immediately trigger a non-GSS
+                 * rekey of our own, to set one up, before the session
+                 * keys have been used for anything else.
+                 *
+                 * This is similar to the cross-certification done at
+                 * user request in the permanent host key cache, but
+                 * here we do it automatically, once, at session
+                 * startup, and only add the key to the transient
+                 * cache.
+                 */
+                if (s->hostkey_alg) {
+                    s->need_gss_transient_hostkey = TRUE;
+                } else {
+                    /*
+                     * If we negotiated the "null" host key algorithm
+                     * in the key exchange, that's an indication that
+                     * no host key at all is available from the server
+                     * (both because we listed "null" last, and
+                     * because RFC 4462 section 5 says that a server
+                     * MUST NOT offer "null" as a host key algorithm
+                     * unless that is the only algorithm it provides
+                     * at all).
+                     *
+                     * In that case we actually _can't_ perform a
+                     * non-GSSAPI key exchange, so it's pointless to
+                     * attempt one proactively. This is also likely to
+                     * cause trouble later if a rekey is required at a
+                     * moment whne GSS credentials are not available,
+                     * but someone setting up a server in this
+                     * configuration presumably accepts that as a
+                     * consequence.
+                     */
+                    if (!s->warned_about_no_gss_transient_hostkey) {
+                        ppl_logevent(("No fallback host key available"));
+                        s->warned_about_no_gss_transient_hostkey = TRUE;
+                    }
+                }
+            }
+        } else {
+            /*
+             * We've just done a fallback key exchange, so make
+             * sure the host key it used is in the cache of keys
+             * we previously received in GSS kexes.
+             *
+             * An exception is if this was the non-GSS key exchange we
+             * triggered on purpose to populate the transient cache.
+             */
+            assert(s->hkey);  /* only KEXTYPE_GSS lets this be null */
+            s->fingerprint = ssh2_fingerprint(s->hkey);
+
+            if (s->need_gss_transient_hostkey) {
+                ppl_logevent(("Post-GSS rekey provided fallback host key:"));
+                ppl_logevent(("%s", s->fingerprint));
+                ssh_store_transient_hostkey(s, s->hkey);
+                s->need_gss_transient_hostkey = FALSE;
+            } else if (!ssh_verify_transient_hostkey(s, s->hkey)) {
+                ppl_logevent(("Non-GSS rekey after initial GSS kex "
+                              "used host key:"));
+                ppl_logevent(("%s", s->fingerprint));
+                ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any "
+                             "used in previous GSS kex");
+                return;
+            }
+
+            sfree(s->fingerprint);
+            s->fingerprint = NULL;
+        }
+    } else
+#endif /* NO_GSSAPI */
+        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 == s->hostkey_alg)
+                        continue;
+
+                    for (j = 0; j < s->n_uncert_hostkeys; j++)
+                        if (s->uncert_hostkeys[j] == i)
+                            break;
+
+                    if (j < s->n_uncert_hostkeys) {
+                        char *newlist;
+                        if (list)
+                            newlist = dupprintf("%s/%s", list,
+                                                hostkey_algs[i].alg->ssh_id);
+                        else
+                            newlist = dupprintf("%s", hostkey_algs[i].alg->ssh_id);
+                        sfree(list);
+                        list = newlist;
+                        nkeys++;
+                    }
+                }
+                if (list) {
+                    ppl_logevent(("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.)
+             */
+            s->fingerprint = ssh2_fingerprint(s->hkey);
+            ppl_logevent(("Host key fingerprint is:"));
+            ppl_logevent(("%s", s->fingerprint));
+            /* First check against manually configured host keys. */
+            s->dlgret = verify_ssh_manual_host_key(
+                s->conf, s->fingerprint, s->hkey);
+            if (s->dlgret == 0) {          /* did not match */
+                ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually "
+                             "configured list");
+                return;
+            } else if (s->dlgret < 0) { /* none configured; use standard handling */
+                s->dlgret = verify_ssh_host_key(s->ppl.frontend,
+                                                s->savedhost, s->savedport,
+                                                ssh_key_cache_id(s->hkey),
+                                                s->keystr, s->fingerprint,
+                                                ssh2_transport_dialog_callback, s);
+#ifdef FUZZING
+                s->dlgret = 1;
+#endif
+                crMaybeWaitUntilV(s->dlgret >= 0);
+                if (s->dlgret == 0) {
+                    ssh_user_close(s->ppl.ssh,
+                                   "User aborted at host key verification");
+                    return;
+                }
+            }
+            sfree(s->fingerprint);
+            s->fingerprint = NULL;
+            /*
+             * Save this host key, to check against the one presented in
+             * subsequent rekeys.
+             */
+            s->hostkey_str = s->keystr;
+            s->keystr = NULL;
+        } else if (s->cross_certifying) {
+            s->fingerprint = ssh2_fingerprint(s->hkey);
+            ppl_logevent(("Storing additional host key for this host:"));
+            ppl_logevent(("%s", s->fingerprint));
+            sfree(s->fingerprint);
+            s->fingerprint = NULL;
+            store_host_key(s->savedhost, s->savedport,
+                           ssh_key_cache_id(s->hkey), s->keystr);
+            s->cross_certifying = FALSE;
+            /*
+             * Don't forget to store the new key as the one we'll be
+             * re-checking in future normal rekeys.
+             */
+            s->hostkey_str = s->keystr;
+            s->keystr = NULL;
+        } 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(s->hostkey_str, s->keystr)) {
+#ifndef FUZZING
+                ssh_sw_abort(s->ppl.ssh,
+                             "Host key was different in repeat key exchange");
+                return;
+#endif
+            }
+        }
+    sfree(s->keystr);
+    s->keystr = NULL;
+    if (s->hkey) {
+        ssh_key_free(s->hkey);
+        s->hkey = NULL;
+    }
+
+    /*
+     * The exchange hash from the very first key exchange is also
+     * the session id, used in session key construction and
+     * authentication.
+     */
+    if (!s->got_session_id) {
+        assert(sizeof(s->exchange_hash) <= sizeof(s->session_id));
+        memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash));
+        s->session_id_len = s->kex_alg->hash->hlen;
+        assert(s->session_id_len <= sizeof(s->session_id));
+        s->got_session_id = TRUE;
+    }
+
+    /*
+     * Send SSH2_MSG_NEWKEYS.
+     */
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS);
+    pq_push(s->ppl.out_pq, pktout);
+    /* Start counting down the outgoing-data limit for these cipher keys. */
+    s->stats->out.running = TRUE;
+    s->stats->out.remaining = s->max_data_size;
+
+    /*
+     * Force the BPP to synchronously marshal all packets up to and
+     * including that NEWKEYS into wire format, before we switch over
+     * to new crypto.
+     */
+    ssh_bpp_handle_output(s->ppl.bpp);
+
+    /*
+     * We've sent client NEWKEYS, so create and initialise
+     * client-to-server session keys.
+     */
+    {
+        strbuf *cipher_key = strbuf_new();
+        strbuf *cipher_iv = strbuf_new();
+        strbuf *mac_key = strbuf_new();
+
+        if (s->out.cipher) {
+            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'A',
+                       s->out.cipher->blksize);
+            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'C',
+                       s->out.cipher->padded_keybytes);
+        }
+        if (s->out.mac) {
+            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'E',
+                       s->out.mac->keylen);
+        }
+
+        ssh2_bpp_new_outgoing_crypto(
+            s->ppl.bpp,
+            s->out.cipher, cipher_key->u, cipher_iv->u,
+            s->out.mac, s->out.etm_mode, mac_key->u,
+            s->out.comp);
+
+        strbuf_free(cipher_key);
+        strbuf_free(cipher_iv);
+        strbuf_free(mac_key);
+    }
+
+    if (s->out.cipher)
+        ppl_logevent(("Initialised %.200s client->server encryption",
+                      s->out.cipher->text_name));
+    if (s->out.mac)
+        ppl_logevent(("Initialised %.200s client->server"
+                      " MAC algorithm%s%s",
+                      s->out.mac->text_name,
+                      s->out.etm_mode ? " (in ETM mode)" : "",
+                      (s->out.cipher->required_mac ?
+                       " (required by cipher)" : "")));
+    if (s->out.comp->text_name)
+        ppl_logevent(("Initialised %s compression",
+                      s->out.comp->text_name));
+
+    /*
+     * Now our end of the key exchange is complete, we can send all
+     * our queued higher-layer packets. Transfer the whole of the next
+     * layer's outgoing queue on to our own.
+     */
+    pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+    /*
+     * Expect SSH2_MSG_NEWKEYS from server.
+     */
+    crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+    if (pktin->type != SSH2_MSG_NEWKEYS) {
+        ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+                        "expecting SSH_MSG_NEWKEYS, type %d (%s)",
+                        pktin->type,
+                        ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                      s->ppl.bpp->pls->actx,
+                                      pktin->type));
+        return;
+    }
+    /* Start counting down the incoming-data limit for these cipher keys. */
+    s->stats->in.running = TRUE;
+    s->stats->in.remaining = s->max_data_size;
+
+    /*
+     * We've seen server NEWKEYS, so create and initialise
+     * server-to-client session keys.
+     */
+    {
+        strbuf *cipher_key = strbuf_new();
+        strbuf *cipher_iv = strbuf_new();
+        strbuf *mac_key = strbuf_new();
+
+        if (s->in.cipher) {
+            ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'B',
+                       s->in.cipher->blksize);
+            ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'D',
+                       s->in.cipher->padded_keybytes);
+        }
+        if (s->in.mac) {
+            ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'F',
+                       s->in.mac->keylen);
+        }
+
+        ssh2_bpp_new_incoming_crypto(
+            s->ppl.bpp,
+            s->in.cipher, cipher_key->u, cipher_iv->u,
+            s->in.mac, s->in.etm_mode, mac_key->u,
+            s->in.comp);
+
+        strbuf_free(cipher_key);
+        strbuf_free(cipher_iv);
+        strbuf_free(mac_key);
+    }
+
+    if (s->in.cipher)
+        ppl_logevent(("Initialised %.200s server->client encryption",
+                      s->in.cipher->text_name));
+    if (s->in.mac)
+        ppl_logevent(("Initialised %.200s server->client MAC algorithm%s%s",
+                      s->in.mac->text_name,
+                      s->in.etm_mode ? " (in ETM mode)" : "",
+                      (s->in.cipher->required_mac ?
+                       " (required by cipher)" : "")));
+    if (s->in.comp->text_name)
+        ppl_logevent(("Initialised %s decompression",
+                      s->in.comp->text_name));
+
+    /*
+     * Free shared secret.
+     */
+    freebn(s->K); s->K = NULL;
+
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    update_specials_menu(s->ppl.frontend);
+
+    /*
+     * Key exchange is over. Loop straight back round if we have a
+     * deferred rekey reason.
+     */
+    if (s->deferred_rekey_reason) {
+        ppl_logevent(("%s", s->deferred_rekey_reason));
+        pktin = NULL;
+        s->deferred_rekey_reason = NULL;
+        goto begin_key_exchange;
+    }
+
+    /*
+     * Otherwise, schedule a timer for our next rekey.
+     */
+    s->kex_in_progress = FALSE;
+    s->last_rekey = GETTICKCOUNT();
+    (void) ssh2_transport_timer_update(s, 0);
+
+    /*
+     * Now we're encrypting. Get the next-layer protocol started if it
+     * hasn't already, and then sit here waiting for reasons to go
+     * back to the start and do a repeat key exchange. One of those
+     * reasons is that we receive KEXINIT from the other end; the
+     * other is if we find rekey_reason is non-NULL, i.e. we've
+     * decided to initiate a rekey ourselves for some reason.
+     */
+    if (!s->higher_layer_ok) {
+        pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
+        put_stringz(pktout, s->higher_layer->vt->name);
+        pq_push(s->ppl.out_pq, pktout);
+        crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+        if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
+            ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
+                         "'%s' protocol", s->higher_layer->vt->name);
+            return;
+        }
+
+        s->higher_layer_ok = TRUE;
+        queue_idempotent_callback(&s->higher_layer->ic_process_queue);
+    }
+
+    s->rekey_class = RK_NONE;
+    do {
+        crReturnV;
+
+        /* Pass through outgoing packets from the higher layer. */
+        pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+        /* Wait for either a KEXINIT, or something setting
+         * s->rekey_class. This call to ssh2_transport_pop also has
+         * the side effect of transferring incoming packets _to_ the
+         * higher layer (via filter_queue). */
+        if ((pktin = ssh2_transport_pop(s)) != NULL) {
+            if (pktin->type != SSH2_MSG_KEXINIT) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected transport-"
+                                "layer packet outside a key exchange, "
+                                "type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+            pq_push_front(s->ppl.in_pq, pktin);
+            ppl_logevent(("Server initiated key re-exchange"));
+            s->rekey_class = RK_SERVER;
+        }
+
+        if (s->rekey_class == RK_POST_USERAUTH) {
+            /*
+             * userauth has seen a USERAUTH_SUCCEEDED. For a couple of
+             * reasons, this may be the moment to do an immediate
+             * rekey with different parameters. But it may not; so
+             * here we turn that rekey class into either RK_NONE or
+             * RK_NORMAL.
+             *
+             * One is to turn on delayed compression. We do this by a
+             * rekey to work around a protocol design bug:
+             * draft-miller-secsh-compression-delayed-00 says that you
+             * negotiate delayed compression in the first key
+             * exchange, and both sides start compressing when the
+             * server has sent USERAUTH_SUCCESS. This has a race
+             * condition -- the server can't know when the client has
+             * seen it, and thus which incoming packets it should
+             * treat as compressed.
+             *
+             * Instead, we do the initial key exchange without
+             * offering the delayed methods, but note if the server
+             * offers them; when we get here, if a delayed method was
+             * available that was higher on our list than what we got,
+             * we initiate a rekey in which we _do_ list the delayed
+             * methods (and hopefully get it as a result). Subsequent
+             * rekeys will do the same.
+             *
+             * Another reason for a rekey at this point is if we've
+             * done a GSS key exchange and don't have anything in our
+             * transient hostkey cache, in which case we should make
+             * an attempt to populate the cache now.
+             */
+            assert(!s->userauth_succeeded); /* should only happen once */
+            s->userauth_succeeded = TRUE;
+            if (s->pending_compression) {
+                s->rekey_reason = "enabling delayed compression";
+                s->rekey_class = RK_NORMAL;
+            } else if (s->need_gss_transient_hostkey) {
+                s->rekey_reason = "populating transient host key cache";
+                s->rekey_class = RK_NORMAL;
+            } else {
+                /* No need to rekey at this time. */
+                s->rekey_class = RK_NONE;
+            }
+        }
+
+        if (!s->rekey_class) {
+            /* If we don't yet have any other reason to rekey, check
+             * if we've hit our data limit in either direction. */
+            if (!s->stats->in.running) {
+                s->rekey_reason = "too much data received";
+                s->rekey_class = RK_NORMAL;
+            } else if (!s->stats->out.running) {
+                s->rekey_reason = "too much data sent";
+                s->rekey_class = RK_NORMAL;
+            }
+        }
+
+        if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) {
+            /*
+             * Special case: if the server bug is set that doesn't
+             * allow rekeying, we give a different log message and
+             * continue waiting. (If such a server _initiates_ a
+             * rekey, we process it anyway!)
+             */
+            if ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+                ppl_logevent(("Server bug prevents key re-exchange (%s)",
+                              s->rekey_reason));
+                /* Reset the counters, so that at least this message doesn't
+                 * hit the event log _too_ often. */
+                s->stats->in.running = s->stats->out.running = TRUE;
+                s->stats->in.remaining = s->stats->out.remaining =
+                    s->max_data_size;
+                (void) ssh2_transport_timer_update(s, 0);
+                s->rekey_class = RK_NONE;
+            } else {
+                ppl_logevent(("Initiating key re-exchange (%s)",
+                              s->rekey_reason));
+            }
+        }
+    } while (s->rekey_reason == RK_NONE);
+
+    /* Once we exit the above loop, we really are rekeying. */
+    goto begin_key_exchange;
+
+    crFinishV;
+}
+
+static void ssh2_transport_higher_layer_packet_callback(void *context)
+{
+    PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+    ssh_ppl_process_queue(ppl);
+}
+
+static void ssh2_transport_timer(void *ctx, unsigned long now)
+{
+    struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx;
+    unsigned long mins;
+    unsigned long ticks;
+
+    if (s->kex_in_progress || now != s->next_rekey)
+        return;
+
+    mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+    if (mins == 0)
+	return;
+
+    /* Rekey if enough time has elapsed */
+    ticks = mins * 60 * TICKSPERSEC;
+    if (now - s->last_rekey > ticks - 30*TICKSPERSEC) {
+        s->rekey_reason = "timeout";
+        s->rekey_class = RK_NORMAL;
+        queue_idempotent_callback(&s->ppl.ic_process_queue);
+        return;
+    }
+
+#ifndef NO_GSSAPI
+    /*
+     * Rekey now if we have a new cred or context expires this cycle,
+     * but not if this is unsafe.
+     */
+    if (conf_get_int(s->conf, CONF_gssapirekey)) {
+        ssh2_transport_gss_update(s, FALSE);
+        if ((s->gss_status & GSS_KEX_CAPABLE) != 0 &&
+            (s->gss_status & GSS_CTXT_MAYFAIL) == 0 &&
+            (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) {
+            s->rekey_reason = "GSS credentials updated";
+            s->rekey_class = RK_GSS_UPDATE;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+            return;
+        }
+    }
+#endif
+
+    /* Try again later. */
+    (void) ssh2_transport_timer_update(s, 0);
+}
+
+/*
+ * The rekey_time is zero except when re-configuring.
+ *
+ * We either schedule the next timer and return 0, or return 1 to run the
+ * callback now, which will call us again to re-schedule on completion.
+ */
+static int ssh2_transport_timer_update(struct ssh2_transport_state *s,
+                                       unsigned long rekey_time)
+{
+    unsigned long mins;
+    unsigned long ticks;
+
+    mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+    ticks = mins * 60 * TICKSPERSEC;
+
+    /* Handle change from previous setting */
+    if (rekey_time != 0 && rekey_time != mins) {
+        unsigned long next;
+        unsigned long now = GETTICKCOUNT();
+
+        mins = rekey_time;
+        ticks = mins * 60 * TICKSPERSEC;
+        next = s->last_rekey + ticks;
+
+        /* If overdue, caller will rekey synchronously now */
+        if (now - s->last_rekey > ticks)
+            return 1;
+        ticks = next - now;
+    }
+
+#ifndef NO_GSSAPI
+    if (s->gss_kex_used) {
+        /*
+         * If we've used GSSAPI key exchange, then we should
+         * periodically check whether we need to do another one to
+         * pass new credentials to the server.
+         */
+        unsigned long gssmins;
+
+        /* Check cascade conditions more frequently if configured */
+        gssmins = sanitise_rekey_time(
+            conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+        if (gssmins > 0) {
+            if (gssmins < mins)
+                ticks = (mins = gssmins) * 60 * TICKSPERSEC;
+
+            if ((s->gss_status & GSS_KEX_CAPABLE) != 0) {
+                /*
+                 * Run next timer even sooner if it would otherwise be
+                 * too close to the context expiration time
+                 */
+                if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 &&
+                    s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME)
+                    ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC;
+            }
+        }
+    }
+#endif
+
+    /* Schedule the next timer */
+    s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s);
+    return 0;
+}
+
+static void ssh2_transport_dialog_callback(void *loginv, int ret)
+{
+    struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv;
+    s->dlgret = ret;
+    ssh_ppl_process_queue(&s->ppl);
+}
+
+#ifndef NO_GSSAPI
+/*
+ * This is called at the beginning of each SSH rekey to determine
+ * whether we are GSS capable, and if we did GSS key exchange, and are
+ * delegating credentials, it is also called periodically to determine
+ * whether we should rekey in order to delegate (more) fresh
+ * credentials. This is called "credential cascading".
+ *
+ * On Windows, with SSPI, we may not get the credential expiration, as
+ * Windows automatically renews from cached passwords, so the
+ * credential effectively never expires. Since we still want to
+ * cascade when the local TGT is updated, we use the expiration of a
+ * newly obtained context as a proxy for the expiration of the TGT.
+ */
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+                                      int definitely_rekeying)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    int gss_stat;
+    time_t gss_cred_expiry;
+    unsigned long mins;
+    Ssh_gss_buf gss_sndtok;
+    Ssh_gss_buf gss_rcvtok;
+    Ssh_gss_ctx gss_ctx;
+
+    s->gss_status = 0;
+
+    /*
+     * Nothing to do if no GSSAPI libraries are configured or GSSAPI
+     * auth is not enabled.
+     */
+    if (s->shgss->libs->nlibraries == 0)
+        return;
+    if (!conf_get_int(s->conf, CONF_try_gssapi_auth) &&
+        !conf_get_int(s->conf, CONF_try_gssapi_kex))
+        return;
+
+    /* Import server name and cache it */
+    if (s->shgss->srv_name == GSS_C_NO_NAME) {
+        gss_stat = s->shgss->lib->import_name(
+            s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+        if (gss_stat != SSH_GSS_OK) {
+            if (gss_stat == SSH_GSS_BAD_HOST_NAME)
+                ppl_logevent(("GSSAPI import name failed - Bad service name;"
+                              " won't use GSS key exchange"));
+            else
+                ppl_logevent(("GSSAPI import name failed;"
+                              " won't use GSS key exchange"));
+            return;
+        }
+    }
+
+    /*
+     * Do we (still) have credentials? Capture the credential
+     * expiration when available
+     */
+    gss_stat = s->shgss->lib->acquire_cred(
+        s->shgss->lib, &gss_ctx, &gss_cred_expiry);
+    if (gss_stat != SSH_GSS_OK)
+        return;
+
+    SSH_GSS_CLEAR_BUF(&gss_sndtok);
+    SSH_GSS_CLEAR_BUF(&gss_rcvtok);
+
+    /*
+     * When acquire_cred yields no useful expiration, get a proxy for
+     * the cred expiration from the context expiration.
+     */
+    gss_stat = s->shgss->lib->init_sec_context(
+        s->shgss->lib, &gss_ctx, s->shgss->srv_name,
+        0 /* don't delegate */, &gss_rcvtok, &gss_sndtok,
+        (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL),
+        &s->gss_ctxt_lifetime);
+
+    /* This context was for testing only. */
+    if (gss_ctx)
+        s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx);
+
+    if (gss_stat != SSH_GSS_OK &&
+        gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+        /*
+         * No point in verbosely interrupting the user to tell them we
+         * couldn't get GSS credentials, if this was only a check
+         * between key exchanges to see if fresh ones were available.
+         * When we do do a rekey, this message (if displayed) will
+         * appear among the standard rekey blurb, but when we're not,
+         * it shouldn't pop up all the time regardless.
+         */
+        if (definitely_rekeying)
+            ppl_logevent(("No GSSAPI security context available"));
+
+        return;
+    }
+
+    if (gss_sndtok.length)
+        s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok);
+
+    s->gss_status |= GSS_KEX_CAPABLE;
+
+    /*
+     * When rekeying to cascade, avoding doing this too close to the
+     * context expiration time, since the key exchange might fail.
+     */
+    if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME)
+        s->gss_status |= GSS_CTXT_MAYFAIL;
+
+    /*
+     * If we're not delegating credentials, rekeying is not used to
+     * refresh them. We must avoid setting GSS_CRED_UPDATED or
+     * GSS_CTXT_EXPIRES when credential delegation is disabled.
+     */
+    if (conf_get_int(s->conf, CONF_gssapifwd) == 0)
+        return;
+
+    if (s->gss_cred_expiry != GSS_NO_EXPIRATION &&
+        difftime(gss_cred_expiry, s->gss_cred_expiry) > 0)
+        s->gss_status |= GSS_CRED_UPDATED;
+
+    mins = sanitise_rekey_time(
+        conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+    if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60)
+        s->gss_status |= GSS_CTXT_EXPIRES;
+}
+
+/*
+ * Data structure managing host keys in sessions based on GSSAPI KEX.
+ *
+ * In a session we started with a GSSAPI key exchange, the concept of
+ * 'host key' has completely different lifetime and security semantics
+ * from the usual ones. Per RFC 4462 section 2.1, we assume that any
+ * host key delivered to us in the course of a GSSAPI key exchange is
+ * _solely_ there to use as a transient fallback within the same
+ * session, if at the time of a subsequent rekey the GSS credentials
+ * are temporarily invalid and so a non-GSS KEX method has to be used.
+ *
+ * In particular, in a GSS-based SSH deployment, host keys may not
+ * even _be_ persistent identities for the server; it would be
+ * legitimate for a server to generate a fresh one routinely if it
+ * wanted to, like SSH-1 server keys.
+ *
+ * So, in this mode, we never touch the persistent host key cache at
+ * all, either to check keys against it _or_ to store keys in it.
+ * Instead, we maintain an in-memory cache of host keys that have been
+ * mentioned in GSS key exchanges within this particular session, and
+ * we permit precisely those host keys in non-GSS rekeys.
+ */
+struct ssh_transient_hostkey_cache_entry {
+    const ssh_keyalg *alg;
+    strbuf *pub_blob;
+};
+
+static int ssh_transient_hostkey_cache_cmp(void *av, void *bv)
+{
+    const struct ssh_transient_hostkey_cache_entry
+        *a = (const struct ssh_transient_hostkey_cache_entry *)av,
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(a->alg->ssh_id, b->alg->ssh_id);
+}
+
+static int ssh_transient_hostkey_cache_find(void *av, void *bv)
+{
+    const ssh_keyalg *aalg = (const ssh_keyalg *)av;
+    const struct ssh_transient_hostkey_cache_entry
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(aalg->ssh_id, b->alg->ssh_id);
+}
+
+static void ssh_init_transient_hostkey_store(
+    struct ssh2_transport_state *s)
+{
+    s->transient_hostkey_cache =
+        newtree234(ssh_transient_hostkey_cache_cmp);
+}
+
+static void ssh_cleanup_transient_hostkey_store(
+    struct ssh2_transport_state *s)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    while ((ent = delpos234(s->transient_hostkey_cache, 0)) != NULL) {
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+    freetree234(s->transient_hostkey_cache);
+}
+
+static void ssh_store_transient_hostkey(
+    struct ssh2_transport_state *s, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent, *retd;
+
+    if ((ent = find234(s->transient_hostkey_cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+
+    ent = snew(struct ssh_transient_hostkey_cache_entry);
+    ent->alg = ssh_key_alg(key);
+    ent->pub_blob = strbuf_new();
+    ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob));
+    retd = add234(s->transient_hostkey_cache, ent);
+    assert(retd == ent);
+}
+
+static int ssh_verify_transient_hostkey(
+    struct ssh2_transport_state *s, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    int toret = FALSE;
+
+    if ((ent = find234(s->transient_hostkey_cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        strbuf *this_blob = strbuf_new();
+        ssh_key_public_blob(key, BinarySink_UPCAST(this_blob));
+
+        if (this_blob->len == ent->pub_blob->len &&
+            !memcmp(this_blob->s, ent->pub_blob->s,
+                    this_blob->len))
+            toret = TRUE;
+
+        strbuf_free(this_blob);
+    }
+
+    return toret;
+}
+
+static int ssh_have_transient_hostkey(
+    struct ssh2_transport_state *s, const ssh_keyalg *alg)
+{
+    struct ssh_transient_hostkey_cache_entry *ent =
+        find234(s->transient_hostkey_cache, (void *)alg,
+                ssh_transient_hostkey_cache_find);
+    return ent != NULL;
+}
+
+static int ssh_have_any_transient_hostkey(
+    struct ssh2_transport_state *s)
+{
+    return count234(s->transient_hostkey_cache) > 0;
+}
+
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    assert(s->got_session_id);
+    return make_ptrlen(s->session_id, s->session_id_len);
+}
+
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    s->rekey_reason = NULL;            /* will be filled in later */
+    s->rekey_class = RK_POST_USERAUTH;
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+#endif /* NO_GSSAPI */
+
+static int ssh2_transport_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+    int need_separator = FALSE;
+    int toret;
+
+    if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) {
+        need_separator = TRUE;
+        toret = TRUE;
+    }
+
+    /*
+     * Don't bother offering rekey-based specials if we've decided the
+     * remote won't cope with it, since we wouldn't bother sending it
+     * if asked anyway.
+     */
+    if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+        if (need_separator) {
+            add_special(ctx, NULL, SS_SEP, 0);
+            need_separator = FALSE;
+        }
+
+        add_special(ctx, "Repeat key exchange", SS_REKEY, 0);
+        toret = TRUE;
+
+        if (s->n_uncert_hostkeys) {
+            int i;
+
+            add_special(ctx, NULL, SS_SEP, 0);
+            add_special(ctx, "Cache new host key type", SS_SUBMENU, 0);
+            for (i = 0; i < s->n_uncert_hostkeys; i++) {
+                const ssh_keyalg *alg =
+                    hostkey_algs[s->uncert_hostkeys[i]].alg;
+
+                add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]);
+            }
+            add_special(ctx, NULL, SS_EXITMENU, 0);
+        }
+    }
+
+    return toret;
+}
+
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+                                       SessionSpecialCode code, int arg)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    if (code == SS_REKEY) {
+	if (!s->kex_in_progress) {
+            s->rekey_reason = "at user request";
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+	}
+    } else if (code == SS_XCERT) {
+	if (!s->kex_in_progress) {
+            s->hostkey_alg = hostkey_algs[arg].alg;
+            s->cross_certifying = TRUE;
+            s->rekey_reason = "cross-certifying new host key";
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+	}
+    } else {
+        /* Send everything else to the next layer up. This includes
+         * SS_PING/SS_NOP, which we _could_ handle here - but it's
+         * better to put them in the connection layer, so they'll
+         * still work in bare connection mode. */
+        ssh_ppl_special_cmd(s->higher_layer, code, arg);
+    }
+}
+
+/* Safely convert rekey_time to unsigned long minutes */
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def)
+{
+    if (rekey_time < 0 || rekey_time > MAX_TICK_MINS)
+        rekey_time = def;
+    return (unsigned long)rekey_time;
+}
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s)
+{
+    s->max_data_size = parse_blocksize(
+        conf_get_str(s->conf, CONF_ssh_rekey_data));
+}
+
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_transport_state *s;
+    const char *rekey_reason = NULL;
+    int rekey_mandatory = FALSE;
+    unsigned long old_max_data_size, rekey_time;
+    int i;
+
+    assert(ppl->vt == &ssh2_transport_vtable);
+    s = FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    rekey_time = sanitise_rekey_time(
+        conf_get_int(conf, CONF_ssh_rekey_time), 60);
+    if (ssh2_transport_timer_update(s, rekey_time))
+        rekey_reason = "timeout shortened";
+
+    old_max_data_size = s->max_data_size;
+    ssh2_transport_set_max_data_size(s);
+    if (old_max_data_size != s->max_data_size &&
+	s->max_data_size != 0) {
+        if (s->max_data_size < old_max_data_size) {
+            unsigned long diff = old_max_data_size - s->max_data_size;
+
+            /* Intentionally use bitwise OR instead of logical, so
+             * that we decrement both counters even if the first one
+             * runs out */
+            if ((DTS_CONSUME(s->stats, out, diff) != 0) |
+                (DTS_CONSUME(s->stats, in, diff) != 0))
+                rekey_reason = "data limit lowered";
+        } else {
+            unsigned long diff = s->max_data_size - old_max_data_size;
+            if (s->stats->out.running)
+                s->stats->out.remaining += diff;
+            if (s->stats->in.running)
+                s->stats->in.remaining += diff;
+        }
+    }
+
+    if (conf_get_int(s->conf, CONF_compression) !=
+	conf_get_int(conf, CONF_compression)) {
+        rekey_reason = "compression setting changed";
+        rekey_mandatory = TRUE;
+    }
+
+    for (i = 0; i < CIPHER_MAX; i++)
+	if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
+	    conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+        rekey_reason = "cipher settings changed";
+        rekey_mandatory = TRUE;
+    }
+    if (conf_get_int(s->conf, CONF_ssh2_des_cbc) !=
+	conf_get_int(conf, CONF_ssh2_des_cbc)) {
+        rekey_reason = "cipher settings changed";
+        rekey_mandatory = TRUE;
+    }
+
+    conf_free(s->conf);
+    s->conf = conf_copy(conf);
+
+    if (rekey_reason) {
+        if (!s->kex_in_progress) {
+            s->rekey_reason = rekey_reason;
+            s->rekey_class = RK_NORMAL;
+            queue_idempotent_callback(&s->ppl.ic_process_queue);
+        } else if (rekey_mandatory) {
+            s->deferred_rekey_reason = rekey_reason;
+        }
+    }
+
+    /* Also pass the configuration along to our higher layer */
+    ssh_ppl_reconfigure(s->higher_layer, conf);
+}
+
+static int ssh2_transport_want_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    /* Just delegate this to the higher layer */
+    return ssh_ppl_want_user_input(s->higher_layer);
+}
+
+static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_transport_state *s =
+        FROMFIELD(ppl, struct ssh2_transport_state, ppl);
+
+    /* Just delegate this to the higher layer */
+    ssh_ppl_got_user_input(s->higher_layer);
+}

+ 1673 - 0
source/putty/ssh2userauth.c

@@ -0,0 +1,1673 @@
+/*
+ * Packet protocol layer for the SSH-2 userauth protocol (RFC 4252).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshcr.h"
+
+#ifndef NO_GSSAPI
+#include "sshgssc.h"
+#include "sshgss.h"
+#endif
+
+#define BANNER_LIMIT 131072
+
+struct ssh2_userauth_state {
+    int crState;
+
+    PacketProtocolLayer *transport_layer, *successor_layer;
+    Filename *keyfile;
+    int tryagent, change_username;
+    char *hostname, *fullhostname;
+    char *default_username;
+    int try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd;
+
+    ptrlen session_id;
+    enum {
+        AUTH_TYPE_NONE,
+        AUTH_TYPE_PUBLICKEY,
+        AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+        AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+        AUTH_TYPE_PASSWORD,
+        AUTH_TYPE_GSSAPI,      /* always QUIET */
+        AUTH_TYPE_KEYBOARD_INTERACTIVE,
+        AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+    } type;
+    int done_service_req;
+    int need_pw, can_pubkey, can_passwd, can_keyb_inter;
+    int userpass_ret;
+    int tried_pubkey_config, done_agent;
+    struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+    int can_gssapi;
+    int can_gssapi_keyex_auth;
+    int tried_gssapi;
+    int tried_gssapi_keyex_auth;
+    time_t gss_cred_expiry;
+    Ssh_gss_buf gss_buf;
+    Ssh_gss_buf gss_rcvtok, gss_sndtok;
+    Ssh_gss_stat gss_stat;
+#endif
+    int kbd_inter_refused;
+    prompts_t *cur_prompt;
+    int num_prompts;
+    char *username;
+    char *password;
+    int got_username;
+    strbuf *publickey_blob;
+    int privatekey_available, privatekey_encrypted;
+    char *publickey_algorithm;
+    char *publickey_comment;
+    void *agent_response_to_free;
+    ptrlen agent_response;
+    BinarySource asrc[1];          /* for reading SSH agent response */
+    size_t pkblob_pos_in_agent;
+    int keyi, nkeys;
+    ptrlen pk, alg, comment;
+    int len;
+    PktOut *pktout;
+    int want_user_input;
+
+    agent_pending_query *auth_agent_query;
+    bufchain banner;
+
+    PacketProtocolLayer ppl;
+};
+
+static void ssh2_userauth_free(PacketProtocolLayer *); 
+static void ssh2_userauth_process_queue(PacketProtocolLayer *);
+static int ssh2_userauth_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+                                      SessionSpecialCode code, int arg);
+static int ssh2_userauth_want_user_input(PacketProtocolLayer *ppl);
+static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl);
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *);
+static void ssh2_userauth_agent_callback(void *, void *, int);
+static void ssh2_userauth_add_sigblob(
+    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob);
+static void ssh2_userauth_add_session_id(
+    struct ssh2_userauth_state *s, strbuf *sigdata);
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+    struct ssh2_userauth_state *s, const char *authtype);
+#endif
+
+static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = {
+    ssh2_userauth_free,
+    ssh2_userauth_process_queue,
+    ssh2_userauth_get_specials,
+    ssh2_userauth_special_cmd,
+    ssh2_userauth_want_user_input,
+    ssh2_userauth_got_user_input,
+    ssh2_userauth_reconfigure,
+    "ssh-userauth",
+};
+
+PacketProtocolLayer *ssh2_userauth_new(
+    PacketProtocolLayer *successor_layer,
+    const char *hostname, const char *fullhostname,
+    Filename *keyfile, int tryagent,
+    const char *default_username, int change_username,
+    int try_ki_auth,
+    int try_gssapi_auth, int try_gssapi_kex_auth,
+    int gssapi_fwd, struct ssh_connection_shared_gss_state *shgss)
+{
+    struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state);
+    memset(s, 0, sizeof(*s));
+    s->ppl.vt = &ssh2_userauth_vtable;
+
+    s->successor_layer = successor_layer;
+    s->hostname = dupstr(hostname);
+    s->fullhostname = dupstr(fullhostname);
+    s->keyfile = filename_copy(keyfile);
+    s->tryagent = tryagent;
+    s->default_username = dupstr(default_username);
+    s->change_username = change_username;
+    s->try_ki_auth = try_ki_auth;
+    s->try_gssapi_auth = try_gssapi_auth;
+    s->try_gssapi_kex_auth = try_gssapi_kex_auth;
+    s->gssapi_fwd = gssapi_fwd;
+    s->shgss = shgss;
+    bufchain_init(&s->banner);
+
+    return &s->ppl;
+}
+
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+                                       PacketProtocolLayer *transport)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(userauth, struct ssh2_userauth_state, ppl);
+    s->transport_layer = transport;
+}
+
+static void ssh2_userauth_free(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(ppl, struct ssh2_userauth_state, ppl);
+    bufchain_clear(&s->banner);
+
+    if (s->successor_layer)
+        ssh_ppl_free(s->successor_layer);
+
+    sfree(s->agent_response_to_free);
+    if (s->auth_agent_query)
+        agent_cancel_query(s->auth_agent_query);
+    filename_free(s->keyfile);
+    sfree(s->default_username);
+    sfree(s->hostname);
+    sfree(s->fullhostname);
+    sfree(s);
+}
+
+static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s)
+{
+    PktIn *pktin;
+    ptrlen string;
+
+    while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) {
+        switch (pktin->type) {
+          case SSH2_MSG_USERAUTH_BANNER:
+            string = get_string(pktin);
+            if (string.len > BANNER_LIMIT - bufchain_size(&s->banner))
+                string.len = BANNER_LIMIT - bufchain_size(&s->banner);
+	    sanitise_term_data(&s->banner, string.ptr, string.len);
+            pq_pop(s->ppl.in_pq);
+            break;
+
+          default:
+            return;
+        }
+    }
+}
+
+static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s)
+{
+    ssh2_userauth_filter_queue(s);
+    return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(ppl, struct ssh2_userauth_state, ppl);
+    PktIn *pktin;
+
+    ssh2_userauth_filter_queue(s);     /* no matter why we were called */
+
+    crBegin(s->crState);
+
+    s->done_service_req = FALSE;
+#ifndef NO_GSSAPI
+    s->tried_gssapi = FALSE;
+    s->tried_gssapi_keyex_auth = FALSE;
+#endif
+
+    /*
+     * Misc one-time setup for authentication.
+     */
+    s->publickey_blob = NULL;
+    s->session_id = ssh2_transport_get_session_id(s->transport_layer);
+
+    /*
+     * Load the public half of any configured public key file for
+     * later use.
+     */
+    if (!filename_is_null(s->keyfile)) {
+        int keytype;
+        ppl_logevent(("Reading key file \"%.150s\"",
+                      filename_to_str(s->keyfile)));
+        keytype = key_type(s->keyfile);
+        if (keytype == SSH_KEYTYPE_SSH2 ||
+            keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+            keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+            const char *error;
+            s->publickey_blob = strbuf_new();
+            if (ssh2_userkey_loadpub(s->keyfile,
+                                     &s->publickey_algorithm,
+                                     BinarySink_UPCAST(s->publickey_blob),
+                                     &s->publickey_comment, &error)) {
+                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
+                if (!s->privatekey_available)
+                    ppl_logevent(("Key file contains public key only"));
+                s->privatekey_encrypted =
+                    ssh2_userkey_encrypted(s->keyfile, NULL);
+            } else {
+                ppl_logevent(("Unable to load key (%s)", error));
+                ppl_printf(("Unable to load key file \"%s\" (%s)\r\n",
+                            filename_to_str(s->keyfile), error));
+                strbuf_free(s->publickey_blob);
+                s->publickey_blob = NULL;
+            }
+        } else {
+            ppl_logevent(("Unable to use this key file (%s)",
+                          key_type_to_str(keytype)));
+            ppl_printf(("Unable to use key file \"%s\" (%s)\r\n",
+                        filename_to_str(s->keyfile),
+                        key_type_to_str(keytype)));
+            s->publickey_blob = NULL;
+        }
+    }
+
+    /*
+     * Find out about any keys Pageant has (but if there's a public
+     * key configured, filter out all others).
+     */
+    s->nkeys = 0;
+    s->pkblob_pos_in_agent = 0;
+    if (s->tryagent && agent_exists()) {
+        ppl_logevent(("Pageant is running. Requesting keys."));
+
+        /* Request the keys held by the agent. */
+        {
+            strbuf *request = strbuf_new_for_agent_query();
+            put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES);
+            ssh2_userauth_agent_query(s, request);
+            strbuf_free(request);
+            crWaitUntilV(!s->auth_agent_query);
+        }
+        BinarySource_BARE_INIT(
+            s->asrc, s->agent_response.ptr, s->agent_response.len);
+
+        get_uint32(s->asrc); /* skip length field */
+        if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) {
+            int keyi;
+
+            s->nkeys = toint(get_uint32(s->asrc));
+
+            /*
+             * Vet the Pageant response to ensure that the key count
+             * and blob lengths make sense.
+             */
+            if (s->nkeys < 0) {
+                ppl_logevent(("Pageant response contained a negative"
+                              " key count %d", s->nkeys));
+                s->nkeys = 0;
+                goto done_agent_query;
+            } else {
+                ppl_logevent(("Pageant has %d SSH-2 keys", s->nkeys));
+
+                /* See if configured key is in agent. */
+                for (keyi = 0; keyi < s->nkeys; keyi++) {
+                    size_t pos = s->asrc->pos;
+                    ptrlen blob = get_string(s->asrc);
+                    get_string(s->asrc); /* skip comment */
+                    if (get_err(s->asrc)) {
+                        ppl_logevent(("Pageant response was truncated"));
+                        s->nkeys = 0;
+                        goto done_agent_query;
+                    }
+
+                    if (s->publickey_blob &&
+                        blob.len == s->publickey_blob->len &&
+                        !memcmp(blob.ptr, s->publickey_blob->s,
+                                s->publickey_blob->len)) {
+                        ppl_logevent(("Pageant key #%d matches "
+                                      "configured key file", keyi));
+                        s->keyi = keyi;
+                        s->pkblob_pos_in_agent = pos;
+                        break;
+                    }
+                }
+                if (s->publickey_blob && !s->pkblob_pos_in_agent) {
+                    ppl_logevent(("Configured key file not in Pageant"));
+                    s->nkeys = 0;
+                }
+            }
+        } else {
+            ppl_logevent(("Failed to get reply from Pageant"));
+        }
+      done_agent_query:;
+    }
+
+    /*
+     * We repeat this whole loop, including the username prompt,
+     * until we manage a successful authentication. If the user
+     * types the wrong _password_, they can be sent back to the
+     * beginning to try another username, if this is configured on.
+     * (If they specify a username in the config, they are never
+     * asked, even if they do give a wrong password.)
+     * 
+     * I think this best serves the needs of
+     * 
+     *  - the people who have no configuration, no keys, and just
+     *    want to try repeated (username,password) pairs until they
+     *    type both correctly
+     * 
+     *  - people who have keys and configuration but occasionally
+     *    need to fall back to passwords
+     * 
+     *  - people with a key held in Pageant, who might not have
+     *    logged in to a particular machine before; so they want to
+     *    type a username, and then _either_ their key will be
+     *    accepted, _or_ they will type a password. If they mistype
+     *    the username they will want to be able to get back and
+     *    retype it!
+     */
+    s->got_username = FALSE;
+    while (1) {
+        /*
+         * Get a username.
+         */
+        if (s->got_username && s->change_username) {
+            /*
+             * We got a username last time round this loop, and
+             * with change_username turned off we don't try to get
+             * it again.
+             */
+        } else if ((s->username = s->default_username) == NULL) {
+            s->cur_prompt = new_prompts(s->ppl.frontend);
+            s->cur_prompt->to_server = TRUE;
+            s->cur_prompt->name = dupstr("SSH login name");
+            add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); 
+            s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+            while (1) {
+                while (s->userpass_ret < 0 &&
+                       bufchain_size(s->ppl.user_input) > 0)
+                    s->userpass_ret = get_userpass_input(
+                        s->cur_prompt, s->ppl.user_input);
+
+                if (s->userpass_ret >= 0)
+                    break;
+
+                s->want_user_input = TRUE;
+                crReturnV;
+                s->want_user_input = FALSE;
+            }
+            if (!s->userpass_ret) {
+                /*
+                 * get_userpass_input() failed to get a username.
+                 * Terminate.
+                 */
+                free_prompts(s->cur_prompt);
+                ssh_user_close(s->ppl.ssh, "No username provided");
+                return;
+            }
+            s->username = dupstr(s->cur_prompt->prompts[0]->result);
+            free_prompts(s->cur_prompt);
+        } else {
+            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
+                ppl_printf(("Using username \"%s\".\r\n", s->username));
+        }
+        s->got_username = TRUE;
+
+        /*
+         * Send an authentication request using method "none": (a)
+         * just in case it succeeds, and (b) so that we know what
+         * authentication methods we can usefully try next.
+         */
+        s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+        s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+        put_stringz(s->pktout, s->username);
+        put_stringz(s->pktout, "ssh-connection");/* service requested */
+        put_stringz(s->pktout, "none");    /* method */
+        pq_push(s->ppl.out_pq, s->pktout);
+        s->type = AUTH_TYPE_NONE;
+
+        s->tried_pubkey_config = FALSE;
+        s->kbd_inter_refused = FALSE;
+
+        /* Reset agent request state. */
+        s->done_agent = FALSE;
+        if (s->agent_response.ptr) {
+            if (s->pkblob_pos_in_agent) {
+                s->asrc->pos = s->pkblob_pos_in_agent;
+            } else {
+                s->asrc->pos = 9;      /* skip length + type + key count */
+                s->keyi = 0;
+            }
+        }
+
+        while (1) {
+            ptrlen methods;
+
+            methods.ptr = "";
+            methods.len = 0;
+
+            /*
+             * Wait for the result of the last authentication request.
+             */
+            crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+
+            /*
+             * Now is a convenient point to spew any banner material
+             * that we've accumulated. (This should ensure that when
+             * we exit the auth loop, we haven't any left to deal
+             * with.)
+             */
+            {
+                /*
+                 * Don't show the banner if we're operating in
+                 * non-verbose non-interactive mode. (It's probably
+                 * a script, which means nobody will read the
+                 * banner _anyway_, and moreover the printing of
+                 * the banner will screw up processing on the
+                 * output of (say) plink.)
+                 *
+                 * The banner data has been sanitised already by this
+                 * point, so we can safely pass it straight to
+                 * from_backend.
+                 */
+                if (bufchain_size(&s->banner) &&
+                    (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+                    while (bufchain_size(&s->banner) > 0) {
+                        void *data;
+                        int len;
+                        bufchain_prefix(&s->banner, &data, &len);
+                        from_backend(s->ppl.frontend, TRUE, data, len);
+                        bufchain_consume(&s->banner, len);
+                    }
+                }
+                bufchain_clear(&s->banner);
+            }
+            if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+                ppl_logevent(("Access granted"));
+                goto userauth_success;
+            }
+
+            if (pktin->type != SSH2_MSG_USERAUTH_FAILURE &&
+                s->type != AUTH_TYPE_GSSAPI) {
+                ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+                                "in response to authentication request, "
+                                "type %d (%s)", pktin->type,
+                                ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+                                              s->ppl.bpp->pls->actx,
+                                              pktin->type));
+                return;
+            }
+
+            /*
+             * OK, we're now sitting on a USERAUTH_FAILURE message, so
+             * we can look at the string in it and know what we can
+             * helpfully try next.
+             */
+            if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+                methods = get_string(pktin);
+                if (!get_bool(pktin)) {
+                    /*
+                     * We have received an unequivocal Access
+                     * Denied. This can translate to a variety of
+                     * messages, or no message at all.
+                     *
+                     * For forms of authentication which are attempted
+                     * implicitly, by which I mean without printing
+                     * anything in the window indicating that we're
+                     * trying them, we should never print 'Access
+                     * denied'.
+                     *
+                     * If we do print a message saying that we're
+                     * attempting some kind of authentication, it's OK
+                     * to print a followup message saying it failed -
+                     * but the message may sometimes be more specific
+                     * than simply 'Access denied'.
+                     *
+                     * Additionally, if we'd just tried password
+                     * authentication, we should break out of this
+                     * whole loop so as to go back to the username
+                     * prompt (iff we're configured to allow
+                     * username change attempts).
+                     */
+                    if (s->type == AUTH_TYPE_NONE) {
+                        /* do nothing */
+                    } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+                               s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+                        if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+                            ppl_printf(("Server refused our key\r\n"));
+                        ppl_logevent(("Server refused our key"));
+                    } else if (s->type == AUTH_TYPE_PUBLICKEY) {
+                        /* This _shouldn't_ happen except by a
+                         * protocol bug causing client and server to
+                         * disagree on what is a correct signature. */
+                        ppl_printf(("Server refused public-key signature"
+                                    " despite accepting key!\r\n"));
+                        ppl_logevent(("Server refused public-key signature"
+                                      " despite accepting key!"));
+                    } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+                        /* quiet, so no ppl_printf */
+                        ppl_logevent(("Server refused keyboard-interactive "
+                                      "authentication"));
+                    } else if (s->type==AUTH_TYPE_GSSAPI) {
+                        /* always quiet, so no ppl_printf */
+                        /* also, the code down in the GSSAPI block has
+                         * already logged this in the Event Log */
+                    } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
+                        ppl_logevent(("Keyboard-interactive authentication "
+                                      "failed"));
+                        ppl_printf(("Access denied\r\n"));
+                    } else {
+                        assert(s->type == AUTH_TYPE_PASSWORD);
+                        ppl_logevent(("Password authentication failed"));
+                        ppl_printf(("Access denied\r\n"));
+
+                        if (s->change_username) {
+                            /* XXX perhaps we should allow
+                             * keyboard-interactive to do this too? */
+                            goto try_new_username;
+                        }
+                    }
+                } else {
+                    ppl_printf(("Further authentication required\r\n"));
+                    ppl_logevent(("Further authentication required"));
+                }
+
+                s->can_pubkey =
+                    in_commasep_string("publickey", methods.ptr, methods.len);
+                s->can_passwd =
+                    in_commasep_string("password", methods.ptr, methods.len);
+                s->can_keyb_inter =
+                    s->try_ki_auth &&
+                    in_commasep_string("keyboard-interactive",
+                                       methods.ptr, methods.len);
+#ifndef NO_GSSAPI
+                s->can_gssapi =
+                    s->try_gssapi_auth &&
+                    in_commasep_string("gssapi-with-mic",
+                                       methods.ptr, methods.len) &&
+                    s->shgss->libs->nlibraries > 0;
+                s->can_gssapi_keyex_auth =
+                    s->try_gssapi_kex_auth &&
+                    in_commasep_string("gssapi-keyex",
+                                       methods.ptr, methods.len) &&
+                    s->shgss->libs->nlibraries > 0 &&
+                    s->shgss->ctx;
+#endif
+            }
+
+            s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+#ifndef NO_GSSAPI
+            if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) {
+
+                /* gssapi-keyex authentication */
+
+                s->type = AUTH_TYPE_GSSAPI;
+                s->tried_gssapi_keyex_auth = TRUE;
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+                if (s->shgss->lib->gsslogmsg)
+                    ppl_logevent(("%s", s->shgss->lib->gsslogmsg));
+
+                ppl_logevent(("Trying gssapi-keyex..."));
+                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex");
+                pq_push(s->ppl.out_pq, s->pktout);
+                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                s->shgss->ctx = NULL;
+
+                continue;
+            } else
+#endif /* NO_GSSAPI */
+
+            if (s->can_pubkey && !s->done_agent && s->nkeys) {
+
+                /*
+                 * Attempt public-key authentication using a key from Pageant.
+                 */
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+                ppl_logevent(("Trying Pageant key #%d", s->keyi));
+
+                /* Unpack key from agent response */
+                s->pk = get_string(s->asrc);
+                s->comment = get_string(s->asrc);
+                {
+                    BinarySource src[1];
+                    BinarySource_BARE_INIT(src, s->pk.ptr, s->pk.len);
+                    s->alg = get_string(src);
+                }
+
+                /* See if server will accept it */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, "ssh-connection");
+                                                    /* service requested */
+                put_stringz(s->pktout, "publickey");
+                                                    /* method */
+                put_bool(s->pktout, FALSE); /* no signature included */
+                put_stringpl(s->pktout, s->alg);
+                put_stringpl(s->pktout, s->pk);
+                pq_push(s->ppl.out_pq, s->pktout);
+                s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+
+                    /* Offer of key refused, presumably via
+                     * USERAUTH_FAILURE. Requeue for the next iteration. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+
+                } else {
+                    strbuf *agentreq, *sigdata;
+
+                    if (flags & FLAG_VERBOSE)
+                        ppl_printf(("Authenticating with public key "
+                                    "\"%.*s\" from agent\r\n",
+                                    PTRLEN_PRINTF(s->comment)));
+
+                    /*
+                     * Server is willing to accept the key.
+                     * Construct a SIGN_REQUEST.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, "ssh-connection");
+                                                        /* service requested */
+                    put_stringz(s->pktout, "publickey");
+                                                        /* method */
+                    put_bool(s->pktout, TRUE);  /* signature included */
+                    put_stringpl(s->pktout, s->alg);
+                    put_stringpl(s->pktout, s->pk);
+
+                    /* Ask agent for signature. */
+                    agentreq = strbuf_new_for_agent_query();
+                    put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST);
+                    put_stringpl(agentreq, s->pk);
+                    /* Now the data to be signed... */
+                    sigdata = strbuf_new();
+                    ssh2_userauth_add_session_id(s, sigdata);
+                    put_data(sigdata, s->pktout->data + 5,
+                             s->pktout->length - 5);
+                    put_stringsb(agentreq, sigdata);
+                    /* And finally the (zero) flags word. */
+                    put_uint32(agentreq, 0);
+                    ssh2_userauth_agent_query(s, agentreq);
+                    strbuf_free(agentreq);
+                    crWaitUntilV(!s->auth_agent_query);
+
+                    if (s->agent_response.ptr) {
+                        ptrlen sigblob;
+                        BinarySource src[1];
+                        BinarySource_BARE_INIT(src, s->agent_response.ptr,
+                                               s->agent_response.len);
+                        get_uint32(src); /* skip length field */
+                        if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE &&
+                            (sigblob = get_string(src), !get_err(src))) {
+                            ppl_logevent(("Sending Pageant's response"));
+                            ssh2_userauth_add_sigblob(s, s->pktout,
+                                                      s->pk, sigblob);
+                            pq_push(s->ppl.out_pq, s->pktout);
+                            s->type = AUTH_TYPE_PUBLICKEY;
+                        } else {
+                            /* FIXME: less drastic response */
+                            ssh_sw_abort(s->ppl.ssh, "Pageant failed to "
+                                         "provide a signature");
+                            return;
+                        }
+                    }
+                }
+
+                /* Do we have any keys left to try? */
+                if (s->pkblob_pos_in_agent) {
+                    s->done_agent = TRUE;
+                    s->tried_pubkey_config = TRUE;
+                } else {
+                    s->keyi++;
+                    if (s->keyi >= s->nkeys)
+                        s->done_agent = TRUE;
+                }
+
+            } else if (s->can_pubkey && s->publickey_blob &&
+                       s->privatekey_available && !s->tried_pubkey_config) {
+
+                struct ssh2_userkey *key;   /* not live over crReturn */
+                char *passphrase;           /* not live over crReturn */
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+                s->tried_pubkey_config = TRUE;
+
+                /*
+                 * Try the public key supplied in the configuration.
+                 *
+                 * First, offer the public blob to see if the server is
+                 * willing to accept it.
+                 */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, "ssh-connection");
+                                                /* service requested */
+                put_stringz(s->pktout, "publickey");    /* method */
+                put_bool(s->pktout, FALSE);
+                                                /* no signature included */
+                put_stringz(s->pktout, s->publickey_algorithm);
+                put_string(s->pktout, s->publickey_blob->s,
+                           s->publickey_blob->len);
+                pq_push(s->ppl.out_pq, s->pktout);
+                ppl_logevent(("Offered public key"));
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+                    /* Key refused. Give up. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+                    continue; /* process this new message */
+                }
+                ppl_logevent(("Offer of public key accepted"));
+
+                /*
+                 * Actually attempt a serious authentication using
+                 * the key.
+                 */
+                if (flags & FLAG_VERBOSE)
+                    ppl_printf(("Authenticating with public key \"%s\"\r\n",
+                                s->publickey_comment));
+
+                key = NULL;
+                while (!key) {
+                    const char *error;  /* not live over crReturn */
+                    if (s->privatekey_encrypted) {
+                        /*
+                         * Get a passphrase from the user.
+                         */
+                        s->cur_prompt = new_prompts(s->ppl.frontend);
+                        s->cur_prompt->to_server = FALSE;
+                        s->cur_prompt->name = dupstr("SSH key passphrase");
+                        add_prompt(s->cur_prompt,
+                                   dupprintf("Passphrase for key \"%.100s\": ",
+                                             s->publickey_comment),
+                                   FALSE);
+                        s->userpass_ret = get_userpass_input(
+                            s->cur_prompt, NULL);
+                        while (1) {
+                            while (s->userpass_ret < 0 &&
+                                   bufchain_size(s->ppl.user_input) > 0)
+                                s->userpass_ret = get_userpass_input(
+                                    s->cur_prompt, s->ppl.user_input);
+
+                            if (s->userpass_ret >= 0)
+                                break;
+
+                            s->want_user_input = TRUE;
+                            crReturnV;
+                            s->want_user_input = FALSE;
+                        }
+                        if (!s->userpass_ret) {
+                            /* Failed to get a passphrase. Terminate. */
+                            free_prompts(s->cur_prompt);
+                            ssh_bpp_queue_disconnect(
+                                s->ppl.bpp, "Unable to authenticate",
+                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                            ssh_user_close(s->ppl.ssh, "User aborted at "
+                                           "passphrase prompt");
+                            return;
+                        }
+                        passphrase =
+                            dupstr(s->cur_prompt->prompts[0]->result);
+                        free_prompts(s->cur_prompt);
+                    } else {
+                        passphrase = NULL; /* no passphrase needed */
+                    }
+
+                    /*
+                     * Try decrypting the key.
+                     */
+                    key = ssh2_load_userkey(s->keyfile, passphrase, &error);
+                    if (passphrase) {
+                        /* burn the evidence */
+                        smemclr(passphrase, strlen(passphrase));
+                        sfree(passphrase);
+                    }
+                    if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+                        if (passphrase &&
+                            (key == SSH2_WRONG_PASSPHRASE)) {
+                            ppl_printf(("Wrong passphrase\r\n"));
+                            key = NULL;
+                            /* and loop again */
+                        } else {
+                            ppl_printf(("Unable to load private key (%s)\r\n",
+                                        error));
+                            key = NULL;
+                            break; /* try something else */
+                        }
+                    }
+                }
+
+                if (key) {
+                    strbuf *pkblob, *sigdata, *sigblob;
+
+                    /*
+                     * We have loaded the private key and the server
+                     * has announced that it's willing to accept it.
+                     * Hallelujah. Generate a signature and send it.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, "ssh-connection");
+                                                    /* service requested */
+                    put_stringz(s->pktout, "publickey"); /* method */
+                    put_bool(s->pktout, TRUE); /* signature follows */
+                    put_stringz(s->pktout, ssh_key_ssh_id(key->key));
+                    pkblob = strbuf_new();
+                    ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
+                    put_string(s->pktout, pkblob->s, pkblob->len);
+
+                    /*
+                     * The data to be signed is:
+                     *
+                     *   string  session-id
+                     *
+                     * followed by everything so far placed in the
+                     * outgoing packet.
+                     */
+                    sigdata = strbuf_new();
+                    ssh2_userauth_add_session_id(s, sigdata);
+                    put_data(sigdata, s->pktout->data + 5,
+                             s->pktout->length - 5);
+                    sigblob = strbuf_new();
+                    ssh_key_sign(key->key, sigdata->s, sigdata->len,
+                                 BinarySink_UPCAST(sigblob));
+                    strbuf_free(sigdata);
+                    ssh2_userauth_add_sigblob(
+                        s, s->pktout, make_ptrlen(pkblob->s, pkblob->len),
+                        make_ptrlen(sigblob->s, sigblob->len));
+                    strbuf_free(pkblob);
+                    strbuf_free(sigblob);
+
+                    pq_push(s->ppl.out_pq, s->pktout);
+                    ppl_logevent(("Sent public key signature"));
+                    s->type = AUTH_TYPE_PUBLICKEY;
+                    ssh_key_free(key->key);
+                    sfree(key->comment);
+                    sfree(key);
+                }
+
+#ifndef NO_GSSAPI
+            } else if (s->can_gssapi && !s->tried_gssapi) {
+
+                /* gssapi-with-mic authentication */
+
+                ptrlen data;
+
+                s->type = AUTH_TYPE_GSSAPI;
+                s->tried_gssapi = TRUE;
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+                if (s->shgss->lib->gsslogmsg)
+                    ppl_logevent(("%s", s->shgss->lib->gsslogmsg));
+
+                /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
+                ppl_logevent(("Trying gssapi-with-mic..."));
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, "ssh-connection");
+                put_stringz(s->pktout, "gssapi-with-mic");
+                ppl_logevent(("Attempting GSSAPI authentication"));
+
+                /* add mechanism info */
+                s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf);
+
+                /* number of GSSAPI mechanisms */
+                put_uint32(s->pktout, 1);
+
+                /* length of OID + 2 */
+                put_uint32(s->pktout, s->gss_buf.length + 2);
+                put_byte(s->pktout, SSH2_GSS_OIDTYPE);
+
+                /* length of OID */
+                put_byte(s->pktout, s->gss_buf.length);
+
+                put_data(s->pktout, s->gss_buf.value, s->gss_buf.length);
+                pq_push(s->ppl.out_pq, s->pktout);
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+                    ppl_logevent(("GSSAPI authentication request refused"));
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    continue;
+                }
+
+                /* check returned packet ... */
+
+                data = get_string(pktin);
+                s->gss_rcvtok.value = (char *)data.ptr;
+                s->gss_rcvtok.length = data.len;
+                if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
+                    ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
+                    ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
+                    memcmp((char *)s->gss_rcvtok.value + 2,
+                           s->gss_buf.value,s->gss_buf.length) ) {
+                    ppl_logevent(("GSSAPI authentication - wrong response "
+                                  "from server"));
+                    continue;
+                }
+
+                /* Import server name if not cached from KEX */
+                if (s->shgss->srv_name == GSS_C_NO_NAME) {
+                    s->gss_stat = s->shgss->lib->import_name(
+                        s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+                    if (s->gss_stat != SSH_GSS_OK) {
+                        if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+                            ppl_logevent(("GSSAPI import name failed -"
+                                          " Bad service name"));
+                        else
+                            ppl_logevent(("GSSAPI import name failed"));
+                        continue;
+                    }
+                }
+
+                /* Allocate our gss_ctx */
+                s->gss_stat = s->shgss->lib->acquire_cred(
+                    s->shgss->lib, &s->shgss->ctx, NULL);
+                if (s->gss_stat != SSH_GSS_OK) {
+                    ppl_logevent(("GSSAPI authentication failed to get "
+                                  "credentials"));
+                    continue;
+                }
+
+                /* initial tokens are empty */
+                SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+                SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+
+                /* now enter the loop */
+                do {
+                    /*
+                     * When acquire_cred yields no useful expiration, go with
+                     * the service ticket expiration.
+                     */
+                    s->gss_stat = s->shgss->lib->init_sec_context
+                        (s->shgss->lib,
+                         &s->shgss->ctx,
+                         s->shgss->srv_name,
+                         s->gssapi_fwd,
+                         &s->gss_rcvtok,
+                         &s->gss_sndtok,
+                         NULL,
+                         NULL);
+
+                    if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+                        s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+                        ppl_logevent(("GSSAPI authentication initialisation "
+                                      "failed"));
+
+                        if (s->shgss->lib->display_status(s->shgss->lib,
+                                s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
+                            ppl_logevent(("%s", (char *)s->gss_buf.value));
+                            sfree(s->gss_buf.value);
+                        }
+
+                        pq_push_front(s->ppl.in_pq, pktin);
+                        break;
+                    }
+                    ppl_logevent(("GSSAPI authentication initialised"));
+
+                    /*
+                     * Client and server now exchange tokens until GSSAPI
+                     * no longer says CONTINUE_NEEDED
+                     */
+                    if (s->gss_sndtok.length != 0) {
+                        s->pktout =
+                            ssh_bpp_new_pktout(
+                                s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+                        put_string(s->pktout,
+                                   s->gss_sndtok.value, s->gss_sndtok.length);
+                        pq_push(s->ppl.out_pq, s->pktout);
+                        s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+                    }
+
+                    if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+                        crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                        if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+                            ppl_logevent(("GSSAPI authentication -"
+                                          " bad server response"));
+                            s->gss_stat = SSH_GSS_FAILURE;
+                            pq_push_front(s->ppl.in_pq, pktin);
+                            break;
+                        }
+                        data = get_string(pktin);
+                        s->gss_rcvtok.value = (char *)data.ptr;
+                        s->gss_rcvtok.length = data.len;
+                    }
+                } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+                if (s->gss_stat != SSH_GSS_OK) {
+                    s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                    continue;
+                }
+                ppl_logevent(("GSSAPI authentication loop finished OK"));
+
+                /* Now send the MIC */
+
+                s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic");
+                pq_push(s->ppl.out_pq, s->pktout);
+
+                s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+                continue;
+#endif
+            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+                /*
+                 * Keyboard-interactive authentication.
+                 */
+
+                s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER;
+
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, "ssh-connection");
+                                                        /* service requested */
+                put_stringz(s->pktout, "keyboard-interactive");
+                                                        /* method */
+                put_stringz(s->pktout, "");     /* lang */
+                put_stringz(s->pktout, "");     /* submethods */
+                pq_push(s->ppl.out_pq, s->pktout);
+                
+                ppl_logevent(("Attempting keyboard-interactive authentication"));
+
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                    /* Server is not willing to do keyboard-interactive
+                     * at all (or, bizarrely but legally, accepts the
+                     * user without actually issuing any prompts).
+                     * Give up on it entirely. */
+                    pq_push_front(s->ppl.in_pq, pktin);
+                    s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+                    s->kbd_inter_refused = TRUE; /* don't try it again */
+                    continue;
+                }
+
+                /*
+                 * Loop while the server continues to send INFO_REQUESTs.
+                 */
+                while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+
+                    ptrlen name, inst;
+                    int i;
+
+                    /*
+                     * We've got a fresh USERAUTH_INFO_REQUEST.
+                     * Get the preamble and start building a prompt.
+                     */
+                    name = get_string(pktin);
+                    inst = get_string(pktin);
+                    get_string(pktin); /* skip language tag */
+                    s->cur_prompt = new_prompts(s->ppl.frontend);
+                    s->cur_prompt->to_server = TRUE;
+
+                    /*
+                     * Get any prompt(s) from the packet.
+                     */
+                    s->num_prompts = get_uint32(pktin);
+                    for (i = 0; i < s->num_prompts; i++) {
+                        ptrlen prompt;
+                        int echo;
+                        static char noprompt[] =
+                            "<server failed to send prompt>: ";
+
+                        prompt = get_string(pktin);
+                        echo = get_bool(pktin);
+                        if (!prompt.len) {
+                            prompt.ptr = noprompt;
+                            prompt.len = lenof(noprompt)-1;
+                        }
+                        add_prompt(s->cur_prompt, mkstr(prompt), echo);
+                    }
+
+                    if (name.len) {
+                        /* FIXME: better prefix to distinguish from
+                         * local prompts? */
+                        s->cur_prompt->name =
+                            dupprintf("SSH server: %.*s", PTRLEN_PRINTF(name));
+                        s->cur_prompt->name_reqd = TRUE;
+                    } else {
+                        s->cur_prompt->name =
+                            dupstr("SSH server authentication");
+                        s->cur_prompt->name_reqd = FALSE;
+                    }
+                    /* We add a prefix to try to make it clear that a prompt
+                     * has come from the server.
+                     * FIXME: ugly to print "Using..." in prompt _every_
+                     * time round. Can this be done more subtly? */
+                    /* Special case: for reasons best known to themselves,
+                     * some servers send k-i requests with no prompts and
+                     * nothing to display. Keep quiet in this case. */
+                    if (s->num_prompts || name.len || inst.len) {
+                        s->cur_prompt->instruction =
+                            dupprintf("Using keyboard-interactive "
+                                      "authentication.%s%.*s",
+                                      inst.len ? "\n" : "",
+                                      PTRLEN_PRINTF(inst));
+                        s->cur_prompt->instr_reqd = TRUE;
+                    } else {
+                        s->cur_prompt->instr_reqd = FALSE;
+                    }
+
+                    /*
+                     * Display any instructions, and get the user's
+                     * response(s).
+                     */
+                    s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+                    while (1) {
+                        while (s->userpass_ret < 0 &&
+                               bufchain_size(s->ppl.user_input) > 0)
+                            s->userpass_ret = get_userpass_input(
+                                s->cur_prompt, s->ppl.user_input);
+
+                        if (s->userpass_ret >= 0)
+                            break;
+
+                        s->want_user_input = TRUE;
+                        crReturnV;
+                        s->want_user_input = FALSE;
+                    }
+                    if (!s->userpass_ret) {
+                        /*
+                         * Failed to get responses. Terminate.
+                         */
+                        free_prompts(s->cur_prompt);
+                        ssh_bpp_queue_disconnect(
+                            s->ppl.bpp, "Unable to authenticate",
+                            SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                        ssh_user_close(s->ppl.ssh, "User aborted during "
+                                       "keyboard-interactive authentication");
+                        return;
+                    }
+
+                    /*
+                     * Send the response(s) to the server.
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+                    put_uint32(s->pktout, s->num_prompts);
+                    for (i=0; i < s->num_prompts; i++) {
+                        put_stringz(s->pktout,
+                                    s->cur_prompt->prompts[i]->result);
+                    }
+                    s->pktout->minlen = 256;
+                    pq_push(s->ppl.out_pq, s->pktout);
+
+                    /*
+                     * Free the prompts structure from this iteration.
+                     * If there's another, a new one will be allocated
+                     * when we return to the top of this while loop.
+                     */
+                    free_prompts(s->cur_prompt);
+
+                    /*
+                     * Get the next packet in case it's another
+                     * INFO_REQUEST.
+                     */
+                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+
+                }
+
+                /*
+                 * We should have SUCCESS or FAILURE now.
+                 */
+                pq_push_front(s->ppl.in_pq, pktin);
+
+            } else if (s->can_passwd) {
+
+                /*
+                 * Plain old password authentication.
+                 */
+                int changereq_first_time; /* not live over crReturn */
+
+                s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD;
+
+                s->cur_prompt = new_prompts(s->ppl.frontend);
+                s->cur_prompt->to_server = TRUE;
+                s->cur_prompt->name = dupstr("SSH password");
+                add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+                                                    s->username, s->hostname),
+                           FALSE);
+
+                s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
+                while (1) {
+                    while (s->userpass_ret < 0 &&
+                           bufchain_size(s->ppl.user_input) > 0)
+                        s->userpass_ret = get_userpass_input(
+                            s->cur_prompt, s->ppl.user_input);
+
+                    if (s->userpass_ret >= 0)
+                        break;
+
+                    s->want_user_input = TRUE;
+                    crReturnV;
+                    s->want_user_input = FALSE;
+                }
+                if (!s->userpass_ret) {
+                    /*
+                     * Failed to get responses. Terminate.
+                     */
+                    free_prompts(s->cur_prompt);
+                    ssh_bpp_queue_disconnect(
+                        s->ppl.bpp, "Unable to authenticate",
+                        SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                    ssh_user_close(s->ppl.ssh, "User aborted during password "
+                                   "authentication");
+                    return;
+                }
+                /*
+                 * Squirrel away the password. (We may need it later if
+                 * asked to change it.)
+                 */
+                s->password = dupstr(s->cur_prompt->prompts[0]->result);
+                free_prompts(s->cur_prompt);
+
+                /*
+                 * Send the password packet.
+                 *
+                 * We pad out the password packet to 256 bytes to make
+                 * it harder for an attacker to find the length of the
+                 * user's password.
+                 *
+                 * Anyone using a password longer than 256 bytes
+                 * probably doesn't have much to worry about from
+                 * people who find out how long their password is!
+                 */
+                s->pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                put_stringz(s->pktout, s->username);
+                put_stringz(s->pktout, "ssh-connection");
+                                                        /* service requested */
+                put_stringz(s->pktout, "password");
+                put_bool(s->pktout, FALSE);
+                put_stringz(s->pktout, s->password);
+                s->pktout->minlen = 256;
+                pq_push(s->ppl.out_pq, s->pktout);
+                ppl_logevent(("Sent password"));
+                s->type = AUTH_TYPE_PASSWORD;
+
+                /*
+                 * Wait for next packet, in case it's a password change
+                 * request.
+                 */
+                crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                changereq_first_time = TRUE;
+
+                while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+
+                    /* 
+                     * We're being asked for a new password
+                     * (perhaps not for the first time).
+                     * Loop until the server accepts it.
+                     */
+
+                    int got_new = FALSE; /* not live over crReturn */
+                    ptrlen prompt;  /* not live over crReturn */
+                    
+                    {
+                        const char *msg;
+                        if (changereq_first_time)
+                            msg = "Server requested password change";
+                        else
+                            msg = "Server rejected new password";
+                        ppl_logevent(("%s", msg));
+                        ppl_printf(("%s\r\n", msg));
+                    }
+
+                    prompt = get_string(pktin);
+
+                    s->cur_prompt = new_prompts(s->ppl.frontend);
+                    s->cur_prompt->to_server = TRUE;
+                    s->cur_prompt->name = dupstr("New SSH password");
+                    s->cur_prompt->instruction = mkstr(prompt);
+                    s->cur_prompt->instr_reqd = TRUE;
+                    /*
+                     * There's no explicit requirement in the protocol
+                     * for the "old" passwords in the original and
+                     * password-change messages to be the same, and
+                     * apparently some Cisco kit supports password change
+                     * by the user entering a blank password originally
+                     * and the real password subsequently, so,
+                     * reluctantly, we prompt for the old password again.
+                     *
+                     * (On the other hand, some servers don't even bother
+                     * to check this field.)
+                     */
+                    add_prompt(s->cur_prompt,
+                               dupstr("Current password (blank for previously entered password): "),
+                               FALSE);
+                    add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+                               FALSE);
+                    add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+                               FALSE);
+
+                    /*
+                     * Loop until the user manages to enter the same
+                     * password twice.
+                     */
+                    while (!got_new) {
+                        s->userpass_ret = get_userpass_input(
+                            s->cur_prompt, NULL);
+                        while (1) {
+                            while (s->userpass_ret < 0 &&
+                                   bufchain_size(s->ppl.user_input) > 0)
+                                s->userpass_ret = get_userpass_input(
+                                    s->cur_prompt, s->ppl.user_input);
+
+                            if (s->userpass_ret >= 0)
+                                break;
+
+                            s->want_user_input = TRUE;
+                            crReturnV;
+                            s->want_user_input = FALSE;
+                        }
+                        if (!s->userpass_ret) {
+                            /*
+                             * Failed to get responses. Terminate.
+                             */
+                            /* burn the evidence */
+                            free_prompts(s->cur_prompt);
+                            smemclr(s->password, strlen(s->password));
+                            sfree(s->password);
+                            ssh_bpp_queue_disconnect(
+                                s->ppl.bpp, "Unable to authenticate",
+                                SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+                            ssh_user_close(s->ppl.ssh, "User aborted during "
+                                           "password changing");
+                            return;
+                        }
+
+                        /*
+                         * If the user specified a new original password
+                         * (IYSWIM), overwrite any previously specified
+                         * one.
+                         * (A side effect is that the user doesn't have to
+                         * re-enter it if they louse up the new password.)
+                         */
+                        if (s->cur_prompt->prompts[0]->result[0]) {
+                            smemclr(s->password, strlen(s->password));
+                                /* burn the evidence */
+                            sfree(s->password);
+                            s->password =
+                                dupstr(s->cur_prompt->prompts[0]->result);
+                        }
+
+                        /*
+                         * Check the two new passwords match.
+                         */
+                        got_new = (strcmp(s->cur_prompt->prompts[1]->result,
+                                          s->cur_prompt->prompts[2]->result)
+                                   == 0);
+                        if (!got_new)
+                            /* They don't. Silly user. */
+                            ppl_printf(("Passwords do not match\r\n"));
+
+                    }
+
+                    /*
+                     * Send the new password (along with the old one).
+                     * (see above for padding rationale)
+                     */
+                    s->pktout = ssh_bpp_new_pktout(
+                        s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+                    put_stringz(s->pktout, s->username);
+                    put_stringz(s->pktout, "ssh-connection");
+                                                        /* service requested */
+                    put_stringz(s->pktout, "password");
+                    put_bool(s->pktout, TRUE);
+                    put_stringz(s->pktout, s->password);
+                    put_stringz(s->pktout,
+                                       s->cur_prompt->prompts[1]->result);
+                    free_prompts(s->cur_prompt);
+                    s->pktout->minlen = 256;
+                    pq_push(s->ppl.out_pq, s->pktout);
+                    ppl_logevent(("Sent new password"));
+                    
+                    /*
+                     * Now see what the server has to say about it.
+                     * (If it's CHANGEREQ again, it's not happy with the
+                     * new password.)
+                     */
+                    crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+                    changereq_first_time = FALSE;
+
+                }
+
+                /*
+                 * We need to reexamine the current pktin at the top
+                 * of the loop. Either:
+                 *  - we weren't asked to change password at all, in
+                 *    which case it's a SUCCESS or FAILURE with the
+                 *    usual meaning
+                 *  - we sent a new password, and the server was
+                 *    either OK with it (SUCCESS or FAILURE w/partial
+                 *    success) or unhappy with the _old_ password
+                 *    (FAILURE w/o partial success)
+                 * In any of these cases, we go back to the top of
+                 * the loop and start again.
+                 */
+                pq_push_front(s->ppl.in_pq, pktin);
+
+                /*
+                 * We don't need the old password any more, in any
+                 * case. Burn the evidence.
+                 */
+                smemclr(s->password, strlen(s->password));
+                sfree(s->password);
+
+            } else {
+                ssh_bpp_queue_disconnect(
+                    s->ppl.bpp,
+                    "No supported authentication methods available",
+                    SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE);
+                ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
+                             "available (server sent: %.*s)",
+                             PTRLEN_PRINTF(methods));
+                return;
+            }
+
+        }
+      try_new_username:;
+    }
+
+  userauth_success:
+    /*
+     * We've just received USERAUTH_SUCCESS, and we haven't sent
+     * any packets since. Signal the transport layer to consider
+     * doing an immediate rekey, if it has any reason to want to.
+     */
+    ssh2_transport_notify_auth_done(s->transport_layer);
+
+    /*
+     * Finally, hand over to our successor layer, and return
+     * immediately without reaching the crFinishV: ssh_ppl_replace
+     * will have freed us, so crFinishV's zeroing-out of crState would
+     * be a use-after-free bug.
+     */
+    {
+        PacketProtocolLayer *successor = s->successor_layer;
+        s->successor_layer = NULL;     /* avoid freeing it ourself */
+        ssh_ppl_replace(&s->ppl, successor);
+        return;   /* we've just freed s, so avoid even touching s->crState */
+    }
+
+    crFinishV;
+}
+
+static void ssh2_userauth_add_session_id(
+    struct ssh2_userauth_state *s, strbuf *sigdata)
+{
+    if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
+        put_data(sigdata, s->session_id.ptr, s->session_id.len);
+    } else {
+        put_stringpl(sigdata, s->session_id);
+    }
+}
+
+static void ssh2_userauth_agent_query(
+    struct ssh2_userauth_state *s, strbuf *req)
+{
+    void *response;
+    int response_len;
+
+    sfree(s->agent_response_to_free);
+    s->agent_response_to_free = NULL;
+
+    s->auth_agent_query = agent_query(req, &response, &response_len,
+                                      ssh2_userauth_agent_callback, s);
+    if (!s->auth_agent_query)
+        ssh2_userauth_agent_callback(s, response, response_len);
+}
+
+static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen)
+{
+    struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav;
+
+    s->auth_agent_query = NULL;
+    s->agent_response_to_free = reply;
+    s->agent_response = make_ptrlen(reply, replylen);
+
+    queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+/*
+ * Helper function to add an SSH-2 signature blob to a packet. Expects
+ * to be shown the public key blob as well as the signature blob.
+ * Normally just appends the sig blob unmodified as a string, except
+ * that it optionally breaks it open and fiddle with it to work around
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_userauth_add_sigblob(
+    struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob)
+{
+    BinarySource pk[1], sig[1];
+    BinarySource_BARE_INIT(pk, pkblob.ptr, pkblob.len);
+    BinarySource_BARE_INIT(sig, sigblob.ptr, sigblob.len);
+
+    /* dmemdump(pkblob, pkblob_len); */
+    /* dmemdump(sigblob, sigblob_len); */
+
+    /*
+     * See if this is in fact an ssh-rsa signature and a buggy
+     * server; otherwise we can just do this the easy way.
+     */
+    if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) &&
+        ptrlen_eq_string(get_string(pk), "ssh-rsa") &&
+        ptrlen_eq_string(get_string(sig), "ssh-rsa")) {
+	ptrlen mod_mp, sig_mp;
+        size_t sig_prefix_len;
+
+	/*
+	 * Find the modulus and signature integers.
+	 */
+        get_string(pk);                /* skip over exponent */
+        mod_mp = get_string(pk);       /* remember modulus */
+        sig_prefix_len = sig->pos;
+	sig_mp = get_string(sig);
+        if (get_err(pk) || get_err(sig))
+            goto give_up;
+
+        /*
+         * Find the byte length of the modulus, not counting leading
+	 * zeroes.
+         */
+	while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) {
+            mod_mp.len--;
+            mod_mp.ptr = (const char *)mod_mp.ptr + 1;
+        }
+
+	/* debug(("modulus length is %d\n", len)); */
+	/* debug(("signature length is %d\n", siglen)); */
+
+	if (mod_mp.len != sig_mp.len) {
+            strbuf *substr = strbuf_new();
+	    put_data(substr, sigblob.ptr, sig_prefix_len);
+	    put_uint32(substr, mod_mp.len);
+	    put_padding(substr, mod_mp.len - sig_mp.len, 0);
+	    put_data(substr, sig_mp.ptr, sig_mp.len);
+            put_stringsb(pkt, substr);
+	    return;
+	}
+
+	/* 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:;
+    }
+
+    put_stringpl(pkt, sigblob);
+}
+
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+    struct ssh2_userauth_state *s, const char *authtype)
+{
+    strbuf *sb;
+    PktOut *p;
+    Ssh_gss_buf buf;
+    Ssh_gss_buf mic;
+
+    /*
+     * The mic is computed over the session id + intended
+     * USERAUTH_REQUEST packet.
+     */
+    sb = strbuf_new();
+    put_stringpl(sb, s->session_id);
+    put_byte(sb, SSH2_MSG_USERAUTH_REQUEST);
+    put_stringz(sb, s->username);
+    put_stringz(sb, "ssh-connection");
+    put_stringz(sb, authtype);
+
+    /* Compute the mic */
+    buf.value = sb->s;
+    buf.length = sb->len;
+    s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic);
+    strbuf_free(sb);
+
+    /* Now we can build the real packet */
+    if (strcmp(authtype, "gssapi-with-mic") == 0) {
+        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC);
+    } else {
+        p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+        put_stringz(p, s->username);
+        put_stringz(p, "ssh-connection");
+        put_stringz(p, authtype);
+    }
+    put_string(p, mic.value, mic.length);
+
+    return p;
+}
+#endif
+
+static int ssh2_userauth_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    /* No specials provided by this layer. */
+    return FALSE;
+}
+
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+                                      SessionSpecialCode code, int arg)
+{
+    /* No specials provided by this layer. */
+}
+
+static int ssh2_userauth_want_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(ppl, struct ssh2_userauth_state, ppl);
+    return s->want_user_input;
+}
+
+static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(ppl, struct ssh2_userauth_state, ppl);
+    if (s->want_user_input)
+        queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+    struct ssh2_userauth_state *s =
+        FROMFIELD(ppl, struct ssh2_userauth_state, ppl);
+    ssh_ppl_reconfigure(s->successor_layer, conf);
+}

+ 6 - 2
source/putty/sshbpp.h

@@ -17,6 +17,7 @@ struct BinaryPacketProtocolVtable {
 struct BinaryPacketProtocol {
     const struct BinaryPacketProtocolVtable *vt;
     bufchain *in_raw, *out_raw;
+    int input_eof;   /* set this if in_raw will never be added to again */
     PktInQueue in_pq;
     PktOutQueue out_pq;
     PacketLogSettings *pls;
@@ -35,8 +36,11 @@ struct BinaryPacketProtocol {
 
     int remote_bugs;
 
-    int seen_disconnect;
-    char *error;
+    /* Set this if remote connection closure should not generate an
+     * error message (either because it's not to be treated as an
+     * error at all, or because some other error message has already
+     * been emitted). */
+    int expect_close;
 };
 
 #define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp))

+ 102 - 1
source/putty/sshcommon.c

@@ -9,6 +9,7 @@
 #include "putty.h"
 #include "ssh.h"
 #include "sshbpp.h"
+#include "sshppl.h"
 #include "sshchan.h"
 
 /* ----------------------------------------------------------------------
@@ -656,6 +657,72 @@ const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
 #undef TRANSLATE_KEX
 #undef TRANSLATE_AUTH
 
+/* ----------------------------------------------------------------------
+ * Common helper function for clients and implementations of
+ * PacketProtocolLayer.
+ */
+
+void ssh_logevent_and_free(void *frontend, char *message)
+{
+    logevent(frontend, message);
+    sfree(message);
+}
+
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new)
+{
+    new->bpp = old->bpp;
+    ssh_ppl_setup_queues(new, old->in_pq, old->out_pq);
+    new->selfptr = old->selfptr;
+    new->user_input = old->user_input;
+    new->frontend = old->frontend;
+    new->ssh = old->ssh;
+
+    *new->selfptr = new;
+    ssh_ppl_free(old);
+
+    /* The new layer might need to be the first one that sends a
+     * packet, so trigger a call to its main coroutine immediately. If
+     * it doesn't need to go first, the worst that will do is return
+     * straight away. */
+    queue_idempotent_callback(&new->ic_process_queue);
+}
+
+void ssh_ppl_free(PacketProtocolLayer *ppl)
+{
+    delete_callbacks_for_context(ppl);
+    ppl->vt->free(ppl);
+}
+
+static void ssh_ppl_ic_process_queue_callback(void *context)
+{
+    PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+    ssh_ppl_process_queue(ppl);
+}
+
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+                          PktInQueue *inq, PktOutQueue *outq)
+{
+    ppl->in_pq = inq;
+    ppl->out_pq = outq;
+    ppl->in_pq->pqb.ic = &ppl->ic_process_queue;
+    ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback;
+    ppl->ic_process_queue.ctx = ppl;
+
+    /* If there's already something on the input queue, it will want
+     * handling immediately. */
+    if (pq_peek(ppl->in_pq))
+        queue_idempotent_callback(&ppl->ic_process_queue);
+}
+
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text)
+{
+    /* Messages sent via this function are from the SSH layer, not
+     * from the server-side process, so they always have the stderr
+     * flag set. */
+    from_backend(ppl->frontend, TRUE, text, strlen(text));
+    sfree(text);
+}
+
 /* ----------------------------------------------------------------------
  * Common helper functions for clients and implementations of
  * BinaryPacketProtocol.
@@ -677,7 +744,7 @@ void ssh_bpp_common_setup(BinaryPacketProtocol *bpp)
 {
     pq_in_init(&bpp->in_pq, bpp->frontend); // WINSCP
     pq_out_init(&bpp->out_pq, bpp->frontend); // WINSCP
-
+    bpp->input_eof = FALSE;
     bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;
     bpp->ic_in_raw.set = get_frontend_callback_set(bpp->frontend);
     bpp->ic_in_raw.ctx = bpp;
@@ -795,3 +862,37 @@ int verify_ssh_manual_host_key(
 
     return 0;
 }
+
+/* ----------------------------------------------------------------------
+ * Common get_specials function for the two SSH-1 layers.
+ */
+
+int ssh1_common_get_specials(
+    PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+    /*
+     * Don't bother offering IGNORE if we've decided the remote
+     * won't cope with it, since we wouldn't bother sending it if
+     * asked anyway.
+     */
+    if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+        add_special(ctx, "IGNORE message", SS_NOP, 0);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/* ----------------------------------------------------------------------
+ * Other miscellaneous utility functions.
+ */
+
+void free_rportfwd(struct ssh_rportfwd *rpf)
+{
+    if (rpf) {
+        sfree(rpf->log_description);
+        sfree(rpf->shost);
+        sfree(rpf->dhost);
+        sfree(rpf);
+    }
+}

+ 12 - 0
source/putty/sshgss.h

@@ -200,6 +200,18 @@ struct ssh_gss_library {
     void *handle;
 };
 
+/*
+ * State that has to be shared between all GSSAPI-using parts of the
+ * same SSH connection, in particular between GSS key exchange and the
+ * subsequent trivial userauth method that reuses its output.
+ */
+struct ssh_connection_shared_gss_state {
+    struct ssh_gss_liblist *libs;
+    struct ssh_gss_library *lib;
+    Ssh_gss_name srv_name;
+    Ssh_gss_ctx ctx;
+};
+
 #endif /* NO_GSSAPI */
 
 #endif /*PUTTY_SSHGSS_H*/

+ 144 - 0
source/putty/sshppl.h

@@ -0,0 +1,144 @@
+/*
+ * Abstraction of the various layers of SSH packet-level protocol,
+ * general enough to take in all three of the main SSH-2 layers and
+ * both of the SSH-1 phases.
+ */
+
+#ifndef PUTTY_SSHPPL_H
+#define PUTTY_SSHPPL_H
+
+typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
+typedef void (*add_special_fn_t)(
+    void *ctx, const char *text, SessionSpecialCode code, int arg);
+
+struct PacketProtocolLayerVtable {
+    void (*free)(PacketProtocolLayer *); 
+    void (*process_queue)(PacketProtocolLayer *ppl);
+    int (*get_specials)(
+        PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+    void (*special_cmd)(
+        PacketProtocolLayer *ppl, SessionSpecialCode code, int arg);
+    int (*want_user_input)(PacketProtocolLayer *ppl);
+    void (*got_user_input)(PacketProtocolLayer *ppl);
+    void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf);
+
+    /* Protocol-level name of this layer. */
+    const char *name;
+};
+
+struct PacketProtocolLayer {
+    const struct PacketProtocolLayerVtable *vt;
+
+    /* Link to the underlying SSH BPP. */
+    BinaryPacketProtocol *bpp;
+
+    /* Queue from which the layer receives its input packets, and one
+     * to put its output packets on. */
+    PktInQueue *in_pq;
+    PktOutQueue *out_pq;
+
+    /* Idempotent callback that in_pq will be linked to, causing a
+     * call to the process_queue method. in_pq points to this, so it
+     * will be automatically triggered by pushing things on the
+     * layer's input queue, but it can also be triggered on purpose. */
+    IdempotentCallback ic_process_queue;
+
+    /* Owner's pointer to this layer. Permits a layer to unilaterally
+     * abdicate in favour of a replacement, by overwriting this
+     * pointer and then freeing itself. */
+    PacketProtocolLayer **selfptr;
+
+    /* Bufchain of keyboard input from the user, for login prompts and
+     * similar. */
+    bufchain *user_input;
+
+    /* Logging and error-reporting facilities. */
+    void *frontend;               /* for logevent, dialog boxes etc */
+    Ssh ssh;   /* for session termination + assorted connection-layer ops */
+
+    /* Known bugs in the remote implementation. */
+    unsigned remote_bugs;
+};
+
+#define ssh_ppl_process_queue(ppl) ((ppl)->vt->process_queue(ppl))
+#define ssh_ppl_get_specials(ppl, add, ctx) \
+    ((ppl)->vt->get_specials(ppl, add, ctx))
+#define ssh_ppl_special_cmd(ppl, code, arg) \
+    ((ppl)->vt->special_cmd(ppl, code, arg))
+#define ssh_ppl_want_user_input(ppl) ((ppl)->vt->want_user_input(ppl))
+#define ssh_ppl_got_user_input(ppl) ((ppl)->vt->got_user_input(ppl))
+#define ssh_ppl_reconfigure(ppl, conf) ((ppl)->vt->reconfigure(ppl, conf))
+
+/* ssh_ppl_free is more than just a macro wrapper on the vtable; it
+ * does centralised parts of the freeing too. */
+void ssh_ppl_free(PacketProtocolLayer *ppl);
+
+/* Helper routine to point a PPL at its input and output queues. Also
+ * sets up the IdempotentCallback on the input queue to trigger a call
+ * to process_queue whenever packets are added to it. */
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+                          PktInQueue *inq, PktOutQueue *outq);
+
+/* Routine a PPL can call to abdicate in favour of a replacement, by
+ * overwriting ppl->selfptr. Has the side effect of freeing 'old', so
+ * if 'old' actually called this (which is likely) then it should
+ * avoid dereferencing itself on return from this function! */
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new);
+
+PacketProtocolLayer *ssh1_login_new(
+    Conf *conf, const char *host, int port,
+    PacketProtocolLayer *successor_layer);
+PacketProtocolLayer *ssh1_connection_new(
+    Ssh ssh, Conf *conf, ConnectionLayer **cl_out);
+
+struct DataTransferStats;
+struct ssh_connection_shared_gss_state;
+PacketProtocolLayer *ssh2_transport_new(
+    Conf *conf, const char *host, int port, const char *fullhostname,
+    const char *client_greeting, const char *server_greeting,
+    struct ssh_connection_shared_gss_state *shgss,
+    struct DataTransferStats *stats,
+    PacketProtocolLayer *higher_layer);
+PacketProtocolLayer *ssh2_userauth_new(
+    PacketProtocolLayer *successor_layer,
+    const char *hostname, const char *fullhostname,
+    Filename *keyfile, int tryagent,
+    const char *default_username, int change_username,
+    int try_ki_auth,
+    int try_gssapi_auth, int try_gssapi_kex_auth,
+    int gssapi_fwd, struct ssh_connection_shared_gss_state *shgss);
+PacketProtocolLayer *ssh2_connection_new(
+    Ssh ssh, ssh_sharing_state *connshare, int is_simple,
+    Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out);
+
+/* Can't put this in the userauth constructor without having a
+ * dependency loop at setup time (transport and userauth can't _both_
+ * be constructed second and given a pointer to the other). */
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+                                       PacketProtocolLayer *transport);
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the Event Log. Assumes a function parameter called 'ppl' is in
+ * scope, and takes a double pair of parens because it passes a whole
+ * argument list to dupprintf. */
+#define ppl_logevent(params) ( \
+        logevent_and_free((ppl)->frontend, dupprintf params))
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the terminal. Also expects 'ppl' to be in scope and takes double
+ * parens. */
+#define ppl_printf(params) \
+    ssh_ppl_user_output_string_and_free(ppl, dupprintf params)
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text);
+
+/* Methods for userauth to communicate back to the transport layer */
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr);
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr);
+
+/* Methods for ssh1login to pass protocol flags to ssh1connection */
+void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags);
+
+/* Shared get_specials method between the two ssh1 layers */
+int ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *);
+
+#endif /* PUTTY_SSHPPL_H */

+ 15 - 6
source/putty/sshverstring.c

@@ -205,7 +205,10 @@ static void ssh_verstring_send(struct ssh_verstring_state *s)
 #define BPP_WAITFOR(minlen) do                          \
     {                                                   \
         crMaybeWaitUntilV(                              \
+            s->bpp.input_eof ||                         \
             bufchain_size(s->bpp.in_raw) >= (minlen));  \
+        if (s->bpp.input_eof)                           \
+            goto eof;                                   \
     } while (0)
 
 void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
@@ -360,13 +363,14 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
          * Unable to agree on a major protocol version at all.
          */
         if (!ssh_version_includes_v2(s->our_protoversion)) {
-            s->bpp.error = dupstr(
-                "SSH protocol version 1 required by our configuration "
-                "but not provided by remote");
+            ssh_sw_abort(s->bpp.ssh,
+                         "SSH protocol version 1 required by our "
+                         "configuration but not provided by remote");
         } else {
-            s->bpp.error = dupstr(
-                "SSH protocol version 2 required by our configuration "
-                "but remote only provides (old, insecure) SSH-1");
+            ssh_sw_abort(s->bpp.ssh,
+                         "SSH protocol version 2 required by our "
+                         "configuration but remote only provides "
+                         "(old, insecure) SSH-1");
         }
         crStopV;
     }
@@ -388,6 +392,11 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
      * done.
      */
     s->receiver->got_ssh_version(s->receiver, s->major_protoversion);
+    return;
+
+  eof:
+    ssh_remote_error(s->bpp.ssh,
+                     "Server unexpectedly closed network connection");
 
     crFinishV;
 }

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio