Browse Source

PuTTY snapshot b94c6a7e (Move client-specific SSH code into new files - 2018-10-21)

Source commit: 462621938b9fbdab594c9e4eb70448f2beb0ef8e
Martin Prikryl 6 years ago
parent
commit
b22988550b

+ 514 - 0
source/putty/ssh1connection-client.c

@@ -0,0 +1,514 @@
+/*
+ * Client-specific parts of the SSH-1 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshchan.h"
+#include "sshcr.h"
+#include "ssh1connection.h"
+
+void ssh1_connection_direction_specific_setup(
+    struct ssh1_connection_state *s)
+{
+    if (!s->mainchan) {
+        /*
+         * Start up the main session, by telling mainchan.c to do it
+         * all just as it would in SSH-2, and translating those
+         * concepts to SSH-1's non-channel-shaped idea of the main
+         * session.
+         */
+        s->mainchan = mainchan_new(
+            &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
+            FALSE /* is_simple */, NULL);
+    }
+}
+
+typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s,
+                                int success, void *ctx);
+
+struct outstanding_succfail {
+    sf_handler_fn_t handler;
+    void *ctx;
+    struct outstanding_succfail *next;
+
+    /*
+     * The 'trivial' flag is set if this handler is in response to a
+     * request for which the SSH-1 protocol doesn't actually specify a
+     * response packet. The client of this system (mainchan.c) will
+     * expect to get an acknowledgment regardless, so we arrange to
+     * send that ack immediately after the rest of the queue empties.
+     */
+    int trivial;
+};
+
+static void ssh1_connection_process_trivial_succfails(void *vs);
+
+static void ssh1_queue_succfail_handler(
+    struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx,
+    int trivial)
+{
+    struct outstanding_succfail *osf = snew(struct outstanding_succfail);
+    osf->handler = handler;
+    osf->ctx = ctx;
+    osf->trivial = trivial;
+    osf->next = NULL;
+    if (s->succfail_tail)
+        s->succfail_tail->next = osf;
+    else
+        s->succfail_head = osf;
+    s->succfail_tail = osf;
+
+    /* In case this one was trivial and the queue was already empty,
+     * we should make sure we run the handler promptly, and the
+     * easiest way is to queue it anyway and then run a trivials pass
+     * by callback. */
+    queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s);
+}
+
+static void ssh1_connection_process_succfail(
+    struct ssh1_connection_state *s, int success)
+{
+    struct outstanding_succfail *prevhead = s->succfail_head;
+    s->succfail_head = s->succfail_head->next;
+    if (!s->succfail_head)
+        s->succfail_tail = NULL;
+    prevhead->handler(s, success, prevhead->ctx);
+    sfree(prevhead);
+}
+
+static void ssh1_connection_process_trivial_succfails(void *vs)
+{
+    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs;
+    while (s->succfail_head && s->succfail_head->trivial)
+        ssh1_connection_process_succfail(s, TRUE);
+}
+
+int ssh1_handle_direction_specific_packet(
+    struct ssh1_connection_state *s, PktIn *pktin)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+    PktOut *pktout;
+    struct ssh1_channel *c;
+    unsigned remid;
+    struct ssh_rportfwd pf, *pfp;
+    ptrlen host, data;
+    int port;
+
+    switch (pktin->type) {
+      case SSH1_SMSG_SUCCESS:
+      case SSH1_SMSG_FAILURE:
+        if (!s->succfail_head) {
+            ssh_remote_error(s->ppl.ssh,
+                             "Received %s with no outstanding request",
+                             ssh1_pkt_type(pktin->type));
+            return TRUE;
+        }
+
+        ssh1_connection_process_succfail(
+            s, pktin->type == SSH1_SMSG_SUCCESS);
+        queue_toplevel_callback(
+            ssh1_connection_process_trivial_succfails, s);
+
+        return TRUE;
+
+      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"));
+        }
+
+        return TRUE;
+
+      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);
+        }
+
+        return TRUE;
+
+      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);
+
+        return TRUE;
+
+      case SSH1_SMSG_STDOUT_DATA:
+      case SSH1_SMSG_STDERR_DATA:
+        data = get_string(pktin);
+        if (!get_err(pktin)) {
+            int bufsize = seat_output(
+                s->ppl.seat, 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);
+            }
+        }
+
+        return TRUE;
+
+      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;
+        }
+        return TRUE;
+
+      default:
+        return FALSE;
+    }
+}
+
+static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s,
+                                            int success, void *ctx)
+{
+    chan_request_response(s->mainchan_chan, success);
+}
+
+static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s,
+                                              int success, void *ctx)
+{
+}
+
+static void ssh1mainchan_queue_response(struct ssh1_connection_state *s,
+                                        int want_reply, int trivial)
+{
+    sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply :
+                               ssh1mainchan_succfail_nowantreply);
+    ssh1_queue_succfail_handler(s, handler, NULL, trivial);
+}
+
+static void ssh1mainchan_request_x11_forwarding(
+    SshChannel *sc, int want_reply, const char *authproto,
+    const char *authdata, int screen_number, int oneshot)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
+    put_stringz(pktout, authproto);
+    put_stringz(pktout, authdata);
+    if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
+        put_uint32(pktout, screen_number);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static void ssh1mainchan_request_agent_forwarding(
+    SshChannel *sc, int want_reply)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(
+        s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static void ssh1mainchan_request_pty(
+    SshChannel *sc, int want_reply, Conf *conf, int w, int h)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    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, h);
+    put_uint32(pktout, w);
+    put_uint32(pktout, 0); /* width in pixels */
+    put_uint32(pktout, 0); /* height in pixels */
+    write_ttymodes_to_packet(
+        BinarySink_UPCAST(pktout), 1,
+        get_ttymodes_from_conf(s->ppl.seat, conf));
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static int ssh1mainchan_send_env_var(
+    SshChannel *sc, int want_reply, const char *var, const char *value)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_start_shell(
+    SshChannel *sc, int want_reply)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, TRUE);
+}
+
+static void ssh1mainchan_start_command(
+    SshChannel *sc, int want_reply, const char *command)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
+    put_stringz(pktout, command);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, TRUE);
+}
+
+static int ssh1mainchan_start_subsystem(
+    SshChannel *sc, int want_reply, const char *subsystem)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static int ssh1mainchan_send_serial_break(
+    SshChannel *sc, int want_reply, int length)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static int ssh1mainchan_send_signal(
+    SshChannel *sc, int want_reply, const char *signame)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_send_terminal_size_change(
+    SshChannel *sc, int w, int h)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
+    put_uint32(pktout, h);
+    put_uint32(pktout, w);
+    put_uint32(pktout, 0); /* width in pixels */
+    put_uint32(pktout, 0); /* height in pixels */
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc)
+{
+}
+
+static int ssh1mainchan_write(SshChannel *sc, const void *data, int len)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
+    put_string(pktout, data, len);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return 0;
+}
+
+static void ssh1mainchan_write_eof(SshChannel *sc)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+static const struct SshChannelVtable ssh1mainchan_vtable = {
+    ssh1mainchan_write,
+    ssh1mainchan_write_eof,
+    NULL /* unclean_close */,
+    NULL /* unthrottle */,
+    NULL /* get_conf */,
+    NULL /* window_override_removed is only used by SSH-2 sharing */,
+    NULL /* x11_sharing_handover, likewise */,
+    ssh1mainchan_request_x11_forwarding,
+    ssh1mainchan_request_agent_forwarding,
+    ssh1mainchan_request_pty,
+    ssh1mainchan_send_env_var,
+    ssh1mainchan_start_shell,
+    ssh1mainchan_start_command,
+    ssh1mainchan_start_subsystem,
+    ssh1mainchan_send_serial_break,
+    ssh1mainchan_send_signal,
+    ssh1mainchan_send_terminal_size_change,
+    ssh1mainchan_hint_channel_is_simple,
+};
+
+static void ssh1_session_confirm_callback(void *vctx)
+{
+    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
+    chan_open_confirmation(s->mainchan_chan);
+}
+
+SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+    s->mainchan_sc.vt = &ssh1mainchan_vtable;
+    s->mainchan_sc.cl = &s->cl;
+    s->mainchan_chan = chan;
+    queue_toplevel_callback(ssh1_session_confirm_callback, s);
+    return &s->mainchan_sc;
+}
+
+static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
+                                   int success, void *ctx)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+    if (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);
+    }
+}
+
+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 =
+        container_of(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, FALSE);
+
+    return rpf;
+}

+ 16 - 632
source/putty/ssh1connection.c

@@ -11,61 +11,7 @@
 #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 */
-
-    /* In SSH-1, the main session doesn't take the form of a 'channel'
-     * according to the wire protocol. But we want to use the same API
-     * for it, so we define an SshChannel here - but one that uses a
-     * separate vtable from the usual one, so it doesn't map to a
-     * struct ssh1_channel as all the others do. */
-    SshChannel mainchan_sc;
-    Channel *mainchan_chan;            /* the other end of mainchan_sc */
-    mainchan *mainchan;                /* and its subtype */
-
-    int got_pty;
-    int ldisc_opts[LD_N_OPTIONS];
-    int stdout_throttling;
-    int want_user_input;
-    int 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;
-};
+#include "ssh1connection.h"
 
 static int ssh1_rportfwd_cmp(void *av, void *bv)
 {
@@ -100,17 +46,11 @@ static const struct PacketProtocolLayerVtable ssh1_connection_vtable = {
     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 *description, const SocketPeerInfo *pi, Channel *chan);
-static SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan);
 static struct X11FakeAuth *ssh1_add_x11_display(
     ConnectionLayer *cl, int authtype, struct X11Display *disp);
 static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
@@ -149,50 +89,6 @@ static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
     ssh1_set_wants_user_input,
 };
 
-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_initiate_close(SshChannel *c, const char *err);
@@ -221,120 +117,12 @@ static const struct SshChannelVtable ssh1channel_vtable = {
     NULL /* hint_channel_is_simple */,
 };
 
-static void ssh1mainchan_request_x11_forwarding(
-    SshChannel *c, int want_reply, const char *authproto,
-    const char *authdata, int screen_number, int oneshot);
-static void ssh1mainchan_request_agent_forwarding(
-    SshChannel *c, int want_reply);
-static void ssh1mainchan_request_pty(
-    SshChannel *c, int want_reply, Conf *conf, int w, int h);
-static int ssh1mainchan_send_env_var(
-    SshChannel *c, int want_reply, const char *var, const char *value);
-static void ssh1mainchan_start_shell(
-    SshChannel *c, int want_reply);
-static void ssh1mainchan_start_command(
-    SshChannel *c, int want_reply, const char *command);
-static int ssh1mainchan_start_subsystem(
-    SshChannel *c, int want_reply, const char *subsystem);
-static int ssh1mainchan_send_env_var(
-    SshChannel *c, int want_reply, const char *var, const char *value);
-static int ssh1mainchan_send_serial_break(
-    SshChannel *c, int want_reply, int length);
-static int ssh1mainchan_send_signal(
-    SshChannel *c, int want_reply, const char *signame);
-static void ssh1mainchan_send_terminal_size_change(
-    SshChannel *c, int w, int h);
-static void ssh1mainchan_hint_channel_is_simple(SshChannel *c);
-static int ssh1mainchan_write(SshChannel *sc, const void *data, int len);
-static void ssh1mainchan_write_eof(SshChannel *sc);
-
-static const struct SshChannelVtable ssh1mainchan_vtable = {
-    ssh1mainchan_write,
-    ssh1mainchan_write_eof,
-    NULL /* unclean_close */,
-    NULL /* unthrottle */,
-    NULL /* get_conf */,
-    NULL /* window_override_removed is only used by SSH-2 sharing */,
-    NULL /* x11_sharing_handover, likewise */,
-    ssh1mainchan_request_x11_forwarding,
-    ssh1mainchan_request_agent_forwarding,
-    ssh1mainchan_request_pty,
-    ssh1mainchan_send_env_var,
-    ssh1mainchan_start_shell,
-    ssh1mainchan_start_command,
-    ssh1mainchan_start_subsystem,
-    ssh1mainchan_send_serial_break,
-    ssh1mainchan_send_signal,
-    ssh1mainchan_send_terminal_size_change,
-    ssh1mainchan_hint_channel_is_simple,
-};
-
-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,
-                                int success, void *ctx);
-struct outstanding_succfail {
-    sf_handler_fn_t handler;
-    void *ctx;
-    struct outstanding_succfail *next;
-
-    /*
-     * The 'trivial' flag is set if this handler is in response to a
-     * request for which the SSH-1 protocol doesn't actually specify a
-     * response packet. The client of this system (mainchan.c) will
-     * expect to get an acknowledgment regardless, so we arrange to
-     * send that ack immediately after the rest of the queue empties.
-     */
-    int trivial;
-};
-
-static void ssh1_connection_process_trivial_succfails(void *vs);
-
-static void ssh1_queue_succfail_handler(
-    struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx,
-    int trivial)
-{
-    struct outstanding_succfail *osf =
-        snew(struct outstanding_succfail);
-    osf->handler = handler;
-    osf->ctx = ctx;
-    osf->trivial = trivial;
-    if (s->succfail_tail)
-        s->succfail_tail->next = osf;
-    else
-        s->succfail_head = osf;
-    s->succfail_tail = osf;
-
-    /* In case this one was trivial and the queue was already empty,
-     * we should make sure we run the handler promptly, and the
-     * easiest way is to queue it anyway and then run a trivials pass
-     * by callback. */
-    queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s);
-}
-
-static void ssh1_connection_process_succfail(
-    struct ssh1_connection_state *s, int success)
-{
-    struct outstanding_succfail *prevhead = s->succfail_head;
-    s->succfail_head = s->succfail_head->next;
-    prevhead->handler(s, success, prevhead->ctx);
-    sfree(prevhead);
-}
-
-static void ssh1_connection_process_trivial_succfails(void *vs)
-{
-    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs;
-    while (s->succfail_head && s->succfail_head->trivial)
-        ssh1_connection_process_succfail(s, TRUE);
-}
-
 static int ssh1_channelcmp(void *av, void *bv)
 {
     const struct ssh1_channel *a = (const struct ssh1_channel *) av;
@@ -357,7 +145,7 @@ static int ssh1_channelfind(void *av, void *bv)
     return 0;
 }
 
-static void ssh1_channel_free(struct ssh1_channel *c)
+void ssh1_channel_free(struct ssh1_channel *c)
 {
     if (c->chan)
         chan_free(c->chan);
@@ -431,17 +219,10 @@ void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags)
 static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)
 {
     PktIn *pktin;
-    PktOut *pktout;
-    ptrlen data, host;
+    ptrlen data;
     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);
+    unsigned localid;
+    int expect_halfopen;
 
     while (1) {
         if (ssh1_common_filter_queue(&s->ppl))
@@ -450,140 +231,6 @@ static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)
             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;
-            }
-
-            ssh1_connection_process_succfail(
-                s, pktin->type == SSH1_SMSG_SUCCESS);
-            queue_toplevel_callback(
-                ssh1_connection_process_trivial_succfails, s);
-
-            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:
@@ -692,37 +339,14 @@ static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)
             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 = seat_output(
-                    s->ppl.seat, 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;
+          default:
+            if (ssh1_handle_direction_specific_packet(s, pktin)) {
+                pq_pop(s->ppl.in_pq);
                 if (ssh1_check_termination(s))
                     return TRUE;
+            } else {
+                return FALSE;
             }
-            pq_pop(s->ppl.in_pq);
-            break;
-
-          default:
-            return FALSE;
         }
     }
 }
@@ -747,14 +371,10 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
     portfwdmgr_config(s->portfwdmgr, s->conf);
     s->portfwdmgr_configured = TRUE;
 
-    /*
-     * Start up the main session, by telling mainchan.c to do it all
-     * just as it would in SSH-2, and translating those concepts to
-     * SSH-1's non-channel-shaped idea of the main session.
-     */
-    s->mainchan = mainchan_new(
-        &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
-        FALSE /* is_simple */, NULL);
+    while (!s->finished_setup) {
+        ssh1_connection_direction_specific_setup(s);
+        crReturnV;
+    }
 
     while (1) {
 
@@ -883,7 +503,7 @@ static void ssh1_channel_destroy(struct ssh1_channel *c)
     queue_toplevel_callback(ssh1_check_termination_callback, s);
 }
 
-static int ssh1_check_termination(struct ssh1_connection_state *s)
+int ssh1_check_termination(struct ssh1_connection_state *s)
 {
     /*
      * Decide whether we should terminate the SSH connection now.
@@ -907,7 +527,7 @@ static int ssh1_check_termination(struct ssh1_connection_state *s)
  * 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)
+void ssh1_channel_init(struct ssh1_channel *c)
 {
     struct ssh1_connection_state *s = c->connlayer;
     c->closes = 0;
@@ -993,187 +613,6 @@ static struct X11FakeAuth *ssh1_add_x11_display(
     return auth;
 }
 
-static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s,
-                                            int success, void *ctx)
-{
-    chan_request_response(s->mainchan_chan, success);
-}
-
-static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s,
-                                              int success, void *ctx)
-{
-}
-
-static void ssh1mainchan_queue_response(struct ssh1_connection_state *s,
-                                        int want_reply, int trivial)
-{
-    sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply :
-                               ssh1mainchan_succfail_nowantreply);
-    ssh1_queue_succfail_handler(s, handler, NULL, trivial);
-}
-
-static void ssh1mainchan_request_x11_forwarding(
-    SshChannel *sc, int want_reply, const char *authproto,
-    const char *authdata, int screen_number, int oneshot)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
-    put_stringz(pktout, authproto);
-    put_stringz(pktout, authdata);
-    if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
-        put_uint32(pktout, screen_number);
-    pq_push(s->ppl.out_pq, pktout);
-
-    ssh1mainchan_queue_response(s, want_reply, FALSE);
-}
-
-static void ssh1mainchan_request_agent_forwarding(
-    SshChannel *sc, int want_reply)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(
-        s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING);
-    pq_push(s->ppl.out_pq, pktout);
-
-    ssh1mainchan_queue_response(s, want_reply, FALSE);
-}
-
-static void ssh1mainchan_request_pty(
-    SshChannel *sc, int want_reply, Conf *conf, int w, int h)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    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, h);
-    put_uint32(pktout, w);
-    put_uint32(pktout, 0); /* width in pixels */
-    put_uint32(pktout, 0); /* height in pixels */
-    write_ttymodes_to_packet(
-        BinarySink_UPCAST(pktout), 1,
-        get_ttymodes_from_conf(s->ppl.seat, conf));
-    pq_push(s->ppl.out_pq, pktout);
-
-    ssh1mainchan_queue_response(s, want_reply, FALSE);
-}
-
-static int ssh1mainchan_send_env_var(
-    SshChannel *sc, int want_reply, const char *var, const char *value)
-{
-    return FALSE;              /* SSH-1 doesn't support this at all */
-}
-
-static void ssh1mainchan_start_shell(
-    SshChannel *sc, int want_reply)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
-    pq_push(s->ppl.out_pq, pktout);
-
-    ssh1mainchan_queue_response(s, want_reply, TRUE);
-}
-
-static void ssh1mainchan_start_command(
-    SshChannel *sc, int want_reply, const char *command)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
-    put_stringz(pktout, command);
-    pq_push(s->ppl.out_pq, pktout);
-
-    ssh1mainchan_queue_response(s, want_reply, TRUE);
-}
-
-static int ssh1mainchan_start_subsystem(
-    SshChannel *sc, int want_reply, const char *subsystem)
-{
-    return FALSE;              /* SSH-1 doesn't support this at all */
-}
-
-static int ssh1mainchan_send_serial_break(
-    SshChannel *sc, int want_reply, int length)
-{
-    return FALSE;              /* SSH-1 doesn't support this at all */
-}
-
-static int ssh1mainchan_send_signal(
-    SshChannel *sc, int want_reply, const char *signame)
-{
-    return FALSE;              /* SSH-1 doesn't support this at all */
-}
-
-static void ssh1mainchan_send_terminal_size_change(SshChannel *sc, int w, int h)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
-    put_uint32(pktout, h);
-    put_uint32(pktout, w);
-    put_uint32(pktout, 0); /* width in pixels */
-    put_uint32(pktout, 0); /* height in pixels */
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static void ssh1mainchan_hint_channel_is_simple(SshChannel *c)
-{
-}
-
-static int ssh1mainchan_write(SshChannel *sc, const void *data, int len)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
-    put_string(pktout, data, len);
-    pq_push(s->ppl.out_pq, pktout);
-
-    return 0;
-}
-
-static void ssh1mainchan_write_eof(SshChannel *sc)
-{
-    struct ssh1_connection_state *s =
-        container_of(sc, struct ssh1_connection_state, mainchan_sc);
-    PktOut *pktout;
-
-    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static void ssh1_session_confirm_callback(void *vctx)
-{
-    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
-    chan_open_confirmation(s->mainchan_chan);
-}
-
-static SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
-{
-    struct ssh1_connection_state *s =
-        container_of(cl, struct ssh1_connection_state, cl);
-    s->mainchan_sc.cl = &s->cl;
-    s->mainchan_sc.vt = &ssh1mainchan_vtable;
-    s->mainchan_chan = chan;
-    queue_toplevel_callback(ssh1_session_confirm_callback, s);
-    return &s->mainchan_sc;
-}
-
 static SshChannel *ssh1_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
     const char *description, const SocketPeerInfo *pi, Channel *chan)
@@ -1203,61 +642,6 @@ static SshChannel *ssh1_lportfwd_open(
     return &c->sc;
 }
 
-static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
-                                   int success, void *ctx)
-{
-    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
-    struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
-
-    if (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 =
-        container_of(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, FALSE);
-
-    return rpf;
-}
-
 static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
 {
     /*

+ 114 - 0
source/putty/ssh1connection.h

@@ -0,0 +1,114 @@
+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 */
+
+    /* In SSH-1, the main session doesn't take the form of a 'channel'
+     * according to the wire protocol. But we want to use the same API
+     * for it, so we define an SshChannel here - but one that uses a
+     * separate vtable from the usual one, so it doesn't map to a
+     * struct ssh1_channel as all the others do. */
+    SshChannel mainchan_sc;
+    Channel *mainchan_chan;            /* the other end of mainchan_sc */
+    mainchan *mainchan;                /* and its subtype */
+
+    int got_pty;
+    int ldisc_opts[LD_N_OPTIONS];
+    int stdout_throttling;
+    int want_user_input;
+    int 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;
+};
+
+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 */
+};
+
+SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan);
+void ssh1_channel_init(struct ssh1_channel *c);
+void ssh1_channel_free(struct ssh1_channel *c);
+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);
+
+void ssh1_connection_direction_specific_setup(
+    struct ssh1_connection_state *s);
+int ssh1_handle_direction_specific_packet(
+    struct ssh1_connection_state *s, PktIn *pktin);
+
+int ssh1_check_termination(struct ssh1_connection_state *s);

+ 2 - 45
source/putty/ssh1login.c

@@ -120,40 +120,6 @@ static void ssh1_login_free(PacketProtocolLayer *ppl)
     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));
-            pq_pop(ppl->in_pq);
-            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);
@@ -229,17 +195,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
         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);
-    }
+    ssh1_compute_session_id(s->session_id, s->cookie,
+                            &s->hostkey, &s->servkey);
 
     for (i = 0; i < 32; i++)
         s->session_key[i] = random_byte();

+ 448 - 0
source/putty/ssh2connection-client.c

@@ -0,0 +1,448 @@
+/*
+ * Client-specific parts of the SSH-2 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshbpp.h"
+#include "sshppl.h"
+#include "sshchan.h"
+#include "sshcr.h"
+#include "ssh2connection.h"
+
+static ChanopenResult chan_open_x11(
+    struct ssh2_connection_state *s, SshChannel *sc,
+    ptrlen peeraddr, int peerport)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    char *peeraddr_str;
+    Channel *ch;
+
+    ppl_logevent(("Received X11 connect request from %.*s:%d",
+                  PTRLEN_PRINTF(peeraddr), peerport));
+
+    if (!s->X11_fwd_enabled && !s->connshare) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("X11 forwarding is not enabled"));
+    }
+
+    peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
+    ch = x11_new_channel(
+        s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
+    sfree(peeraddr_str);
+    ppl_logevent(("Opened X11 forward channel"));
+    CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+static ChanopenResult chan_open_forwarded_tcpip(
+    struct ssh2_connection_state *s, SshChannel *sc,
+    ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    struct ssh_rportfwd pf, *realpf;
+    Channel *ch;
+    char *err;
+
+    ppl_logevent(("Received remote port %.*s:%d open request from %.*s:%d",
+                  PTRLEN_PRINTF(fwdaddr), fwdport,
+                  PTRLEN_PRINTF(peeraddr), peerport));
+
+    pf.shost = mkstr(fwdaddr);
+    pf.sport = fwdport;
+    realpf = find234(s->rportfwds, &pf, NULL);
+    sfree(pf.shost);
+
+    if (realpf == NULL) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("Remote port is not recognised"));
+    }
+
+    if (realpf->share_ctx) {
+        /*
+         * This port forwarding is on behalf of a connection-sharing
+         * downstream.
+         */
+        CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx);
+    }
+
+    err = portfwdmgr_connect(
+        s->portfwdmgr, &ch, realpf->dhost, realpf->dport,
+        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);
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_CONNECT_FAILED,
+            ("Port open failed"));
+    }
+
+    ppl_logevent(("Forwarded port opened successfully"));
+    CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+static ChanopenResult chan_open_auth_agent(
+    struct ssh2_connection_state *s, SshChannel *sc)
+{
+    if (!s->agent_fwd_enabled) {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+            ("Agent forwarding is not enabled"));
+    }
+
+    CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
+}
+
+ChanopenResult ssh2_connection_parse_channel_open(
+    struct ssh2_connection_state *s, ptrlen type,
+    PktIn *pktin, SshChannel *sc)
+{
+    if (ptrlen_eq_string(type, "x11")) {
+        ptrlen peeraddr = get_string(pktin);
+        int peerport = get_uint32(pktin);
+
+        return chan_open_x11(s, sc, peeraddr, peerport);
+    } else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
+        ptrlen fwdaddr = get_string(pktin);
+        int fwdport = toint(get_uint32(pktin));
+        ptrlen peeraddr = get_string(pktin);
+        int peerport = toint(get_uint32(pktin));
+
+        return chan_open_forwarded_tcpip(
+            s, sc, fwdaddr, fwdport, peeraddr, peerport);
+    } else if (ptrlen_eq_string(type, "[email protected]")) {
+        return chan_open_auth_agent(s, sc);
+    } else {
+        CHANOPEN_RETURN_FAILURE(
+            SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
+            ("Unsupported channel type requested"));
+    }
+}
+
+int ssh2_connection_parse_global_request(
+    struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
+{
+    /*
+     * We don't know of any global requests that an SSH client needs
+     * to honour.
+     */
+    return FALSE;
+}
+
+PktOut *ssh2_portfwd_chanopen(
+    struct ssh2_connection_state *s, struct ssh2_channel *c,
+    const char *hostname, int port,
+    const char *description, const SocketPeerInfo *peerinfo)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    PktOut *pktout;
+
+    /*
+     * In client mode, this function is called by portfwdmgr in
+     * response to PortListeners that were set up in
+     * portfwdmgr_config, which means that the hostname and port
+     * parameters will indicate the host we want to tell the server to
+     * connect _to_.
+     */
+
+    ppl_logevent(("Opening connection to %s:%d for %s",
+                  hostname, port, description));
+
+    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);
+
+    return pktout;
+}
+
+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_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);
+    }
+}
+
+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 =
+        container_of(cl, struct ssh2_connection_state, cl);
+    struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+    if (!s->rportfwds)
+        s->rportfwds = newtree234(ssh2_rportfwd_cmp);
+
+    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;
+}
+
+void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+    struct ssh2_connection_state *s =
+        container_of(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);
+    }
+
+    assert(s->rportfwds);
+    struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+    assert(realpf == rpf);
+    free_rportfwd(rpf);
+}
+
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
+{
+    struct ssh2_connection_state *s =
+        container_of(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 main session channel"));
+
+    pktout = ssh2_chanopen_init(c, "session");
+    pq_push(s->ppl.out_pq, pktout);
+
+    return &c->sc;
+}
+
+static void ssh2_channel_response(
+    struct ssh2_channel *c, PktIn *pkt, void *ctx)
+{
+    chan_request_response(c->chan, pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
+}
+
+void ssh2channel_start_shell(SshChannel *sc, int want_reply)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_start_command(
+    SshChannel *sc, int want_reply, const char *command)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, command);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+int ssh2channel_start_subsystem(
+    SshChannel *sc, int want_reply, const char *subsystem)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, subsystem);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return TRUE;
+}
+
+void ssh2channel_request_x11_forwarding(
+    SshChannel *sc, int want_reply, const char *authproto,
+    const char *authdata, int screen_number, int oneshot)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_bool(pktout, oneshot);
+    put_stringz(pktout, authproto);
+    put_stringz(pktout, authdata);
+    put_uint32(pktout, screen_number);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_agent_forwarding(SshChannel *sc, int want_reply)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "[email protected]",
+        want_reply ? ssh2_channel_response : NULL, NULL);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_pty(
+    SshChannel *sc, int want_reply, Conf *conf, int w, int h)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+    strbuf *modebuf;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, conf_get_str(conf, CONF_termtype));
+    put_uint32(pktout, w);
+    put_uint32(pktout, h);
+    put_uint32(pktout, 0);	       /* pixel width */
+    put_uint32(pktout, 0);	       /* pixel height */
+    modebuf = strbuf_new();
+    write_ttymodes_to_packet(
+        BinarySink_UPCAST(modebuf), 2,
+        get_ttymodes_from_conf(s->ppl.seat, conf));
+    put_stringsb(pktout, modebuf);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+int ssh2channel_send_env_var(
+    SshChannel *sc, int want_reply, const char *var, const char *value)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, var);
+    put_stringz(pktout, value);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return TRUE;
+}
+
+int ssh2channel_send_serial_break(SshChannel *sc, int want_reply, int length)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_uint32(pktout, length);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return TRUE;
+}
+
+int ssh2channel_send_signal(
+    SshChannel *sc, int want_reply, const char *signame)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(
+        c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
+    put_stringz(pktout, signame);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return TRUE;
+}
+
+void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
+{
+    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+    struct ssh2_connection_state *s = c->connlayer;
+
+    PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
+    put_uint32(pktout, w);
+    put_uint32(pktout, h);
+    put_uint32(pktout, 0);	       /* pixel width */
+    put_uint32(pktout, 0);	       /* pixel height */
+    pq_push(s->ppl.out_pq, pktout);
+}

+ 31 - 591
source/putty/ssh2connection.c

@@ -10,69 +10,7 @@
 #include "sshppl.h"
 #include "sshchan.h"
 #include "sshcr.h"
-
-struct ssh2_channel;
-
-struct outstanding_global_request;
-
-struct ssh2_connection_state {
-    int crState;
-
-    Ssh *ssh;
-
-    ssh_sharing_state *connshare;
-    char *peer_verstring;
-
-    mainchan *mainchan;
-    SshChannel *mainchan_sc;
-    int ldisc_opts[LD_N_OPTIONS];
-    int session_attempt, session_status;
-    int term_width, term_height;
-    int want_user_input;
-
-    int ssh_is_simple;
-    int persistent;
-
-    Conf *conf;
-
-    tree234 *channels;		       /* indexed by local id */
-    int all_channels_throttled;
-
-    int X11_fwd_enabled;
-    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;
-}
+#include "ssh2connection.h"
 
 static void ssh2_connection_free(PacketProtocolLayer *); 
 static void ssh2_connection_process_queue(PacketProtocolLayer *);
@@ -95,17 +33,9 @@ static const struct PacketProtocolLayerVtable ssh2_connection_vtable = {
     "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 *description, const SocketPeerInfo *pi, Channel *chan);
-static SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
 static struct X11FakeAuth *ssh2_add_x11_display(
     ConnectionLayer *cl, int authtype, struct X11Display *x11disp);
 static struct X11FakeAuth *ssh2_add_sharing_x11_display(
@@ -186,76 +116,6 @@ static char *ssh2_channel_open_failure_error_text(PktIn *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_initiate_close(SshChannel *c, const char *err);
@@ -266,29 +126,6 @@ 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 void ssh2channel_request_x11_forwarding(
-    SshChannel *c, int want_reply, const char *authproto,
-    const char *authdata, int screen_number, int oneshot);
-static void ssh2channel_request_agent_forwarding(
-    SshChannel *c, int want_reply);
-static void ssh2channel_request_pty(
-    SshChannel *c, int want_reply, Conf *conf, int w, int h);
-static int ssh2channel_send_env_var(
-    SshChannel *c, int want_reply, const char *var, const char *value);
-static void ssh2channel_start_shell(
-    SshChannel *c, int want_reply);
-static void ssh2channel_start_command(
-    SshChannel *c, int want_reply, const char *command);
-static int ssh2channel_start_subsystem(
-    SshChannel *c, int want_reply, const char *subsystem);
-static int ssh2channel_send_env_var(
-    SshChannel *c, int want_reply, const char *var, const char *value);
-static int ssh2channel_send_serial_break(
-    SshChannel *c, int want_reply, int length);
-static int ssh2channel_send_signal(
-    SshChannel *c, int want_reply, const char *signame);
-static void ssh2channel_send_terminal_size_change(
-    SshChannel *c, int w, int h);
 static void ssh2channel_hint_channel_is_simple(SshChannel *c);
 
 static const struct SshChannelVtable ssh2channel_vtable = {
@@ -312,12 +149,6 @@ static const struct SshChannelVtable ssh2channel_vtable = {
     ssh2channel_hint_channel_is_simple,
 };
 
-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);
@@ -330,14 +161,12 @@ 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(
+void ssh2_queue_global_request_handler(
     struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx)
 {
     struct outstanding_global_request *ogr =
@@ -436,7 +265,6 @@ PacketProtocolLayer *ssh2_connection_new(
     s->cl.logctx = ssh_get_logctx(ssh);
 
     s->portfwdmgr = portfwdmgr_new(&s->cl);
-    s->rportfwds = newtree234(ssh2_rportfwd_cmp);
 
     *cl_out = &s->cl;
     if (s->connshare)
@@ -468,35 +296,26 @@ static void ssh2_connection_free(PacketProtocolLayer *ppl)
     }
     freetree234(s->x11authtree);
 
-    while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
-        free_rportfwd(rpf);
-    freetree234(s->rportfwds);
+    if (s->rportfwds) {
+        while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+            free_rportfwd(rpf);
+        freetree234(s->rportfwds);
+    }
     portfwdmgr_free(s->portfwdmgr);
 
     sfree(s);
 }
 
-static char *chan_open_x11(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc,
-    ptrlen peeraddr, int peerport);
-static char *chan_open_forwarded_tcpip(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc,
-    ssh_sharing_connstate **share_ctx,
-    ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport);
-static char *chan_open_auth_agent(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc);
-
 static int ssh2_connection_filter_queue(struct ssh2_connection_state *s)
 {
     PktIn *pktin;
     PktOut *pktout;
     ptrlen type, data;
     struct ssh2_channel *c;
-    ssh_sharing_connstate *share_ctx;
     struct outstanding_channel_request *ocr;
     unsigned localid, remid, winsize, pktsize, ext_type;
     int want_reply, reply_success, expect_halfopen;
-    char *error;
+    ChanopenResult chanopen_result;
     PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
 
     /* Cross-reference to ssh2transport.c to handle the common packets
@@ -514,16 +333,11 @@ static int ssh2_connection_filter_queue(struct ssh2_connection_state *s)
 
         switch (pktin->type) {
           case SSH2_MSG_GLOBAL_REQUEST:
-            /* type = */ get_string(pktin);
+            type = get_string(pktin);
             want_reply = get_bool(pktin);
 
-            reply_success = FALSE;
-
-            /*
-             * 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.
-             */
+            reply_success = ssh2_connection_parse_global_request(
+                s, type, pktin);
 
             if (want_reply) {
                 int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS :
@@ -556,69 +370,49 @@ static int ssh2_connection_filter_queue(struct ssh2_connection_state *s)
             break;
 
           case SSH2_MSG_CHANNEL_OPEN:
-            error = NULL;
-
             type = get_string(pktin);
             c = snew(struct ssh2_channel);
             c->connlayer = s;
+            c->chan = NULL;
 
             remid = get_uint32(pktin);
             winsize = get_uint32(pktin);
             pktsize = get_uint32(pktin);
 
-            share_ctx = NULL;
-
-            if (ptrlen_eq_string(type, "x11")) {
-                ptrlen peeraddr = get_string(pktin);
-                int peerport = get_uint32(pktin);
-
-                error = chan_open_x11(
-                    s, &c->chan, &c->sc, peeraddr, peerport);
-            } else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
-                ptrlen fwdaddr = get_string(pktin);
-                int fwdport = toint(get_uint32(pktin));
-                ptrlen peeraddr = get_string(pktin);
-                int peerport = toint(get_uint32(pktin));
-
-                error = chan_open_forwarded_tcpip(
-                    s, &c->chan, &c->sc, &share_ctx,
-                    fwdaddr, fwdport, peeraddr, peerport);
-            } else if (ptrlen_eq_string(type, "[email protected]")) {
-                error = chan_open_auth_agent(s, &c->chan, &c->sc);
-            } else {
-                error = dupstr("Unsupported channel type requested");
-                c->chan = NULL;
-            }
+            chanopen_result = ssh2_connection_parse_channel_open(
+                s, type, pktin, &c->sc);
 
-            if (share_ctx) {
+            if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) {
                 /*
                  * This channel-open request needs to go to a
                  * connection-sharing downstream, so abandon our own
                  * channel-open procedure and just pass the message on
                  * to sshshare.c.
                  */
-                assert(!error);
-                share_got_pkt_from_server(share_ctx, pktin->type,
-                                          BinarySource_UPCAST(pktin)->data,
-                                          BinarySource_UPCAST(pktin)->len);
+                share_got_pkt_from_server(
+                    chanopen_result.u.downstream.share_ctx, pktin->type,
+                    BinarySource_UPCAST(pktin)->data,
+                    BinarySource_UPCAST(pktin)->len);
                 sfree(c);
                 break;
             }
 
             c->remoteid = remid;
             c->halfopen = FALSE;
-            if (error) {
+            if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) {
                 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_uint32(pktout, chanopen_result.u.failure.reason_code);
+                put_stringz(pktout, chanopen_result.u.failure.wire_message);
                 put_stringz(pktout, "en");	/* language tag */
                 pq_push(s->ppl.out_pq, pktout);
-                ppl_logevent(("Rejected channel open: %s", error));
-                sfree(error);
+                ppl_logevent(("Rejected channel open: %s",
+                              chanopen_result.u.failure.wire_message));
+                sfree(chanopen_result.u.failure.wire_message);
                 sfree(c);
             } else {
+                c->chan = chanopen_result.u.success.channel;
                 ssh2_channel_init(c);
                 c->remwindow = winsize;
                 c->remmaxpkt = pktsize;
@@ -1349,7 +1143,7 @@ static void ssh2_check_termination(struct ssh2_connection_state *s)
  * chan untouched (since it will sometimes have been filled in before
  * calling this).
  */
-static void ssh2_channel_init(struct ssh2_channel *c)
+void ssh2_channel_init(struct ssh2_channel *c)
 {
     struct ssh2_connection_state *s = c->connlayer;
     c->closes = 0;
@@ -1370,7 +1164,7 @@ static void ssh2_channel_init(struct ssh2_channel *c)
 /*
  * Construct the common parts of a CHANNEL_OPEN.
  */
-static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
+PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
 {
     struct ssh2_connection_state *s = c->connlayer;
     PktOut *pktout;
@@ -1396,8 +1190,8 @@ static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
  * 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)
+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;
@@ -1516,155 +1310,6 @@ static void ssh2channel_window_override_removed(SshChannel *sc)
     ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
 }
 
-static void ssh2_channel_response(
-    struct ssh2_channel *c, PktIn *pkt, void *ctx)
-{
-    chan_request_response(c->chan, pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
-}
-
-static void ssh2channel_start_shell(
-    SshChannel *sc, int want_reply)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static void ssh2channel_start_command(
-    SshChannel *sc, int want_reply, const char *command)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_stringz(pktout, command);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static int ssh2channel_start_subsystem(
-    SshChannel *sc, int want_reply, const char *subsystem)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_stringz(pktout, subsystem);
-    pq_push(s->ppl.out_pq, pktout);
-
-    return TRUE;
-}
-
-static void ssh2channel_request_x11_forwarding(
-    SshChannel *sc, int want_reply, const char *authproto,
-    const char *authdata, int screen_number, int oneshot)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_bool(pktout, oneshot);
-    put_stringz(pktout, authproto);
-    put_stringz(pktout, authdata);
-    put_uint32(pktout, screen_number);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static void ssh2channel_request_agent_forwarding(
-    SshChannel *sc, int want_reply)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "[email protected]",
-        want_reply ? ssh2_channel_response : NULL, NULL);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static void ssh2channel_request_pty(
-    SshChannel *sc, int want_reply, Conf *conf, int w, int h)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-    strbuf *modebuf;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_stringz(pktout, conf_get_str(conf, CONF_termtype));
-    put_uint32(pktout, w);
-    put_uint32(pktout, h);
-    put_uint32(pktout, 0);	       /* pixel width */
-    put_uint32(pktout, 0);	       /* pixel height */
-    modebuf = strbuf_new();
-    write_ttymodes_to_packet(
-        BinarySink_UPCAST(modebuf), 2,
-        get_ttymodes_from_conf(s->ppl.seat, conf));
-    put_stringsb(pktout, modebuf);
-    pq_push(s->ppl.out_pq, pktout);
-}
-
-static int ssh2channel_send_env_var(
-    SshChannel *sc, int want_reply, const char *var, const char *value)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_stringz(pktout, var);
-    put_stringz(pktout, value);
-    pq_push(s->ppl.out_pq, pktout);
-
-    return TRUE;
-}
-
-static int ssh2channel_send_serial_break(
-    SshChannel *sc, int want_reply, int length)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_uint32(pktout, length);
-    pq_push(s->ppl.out_pq, pktout);
-
-    return TRUE;
-}
-
-static int ssh2channel_send_signal(
-    SshChannel *sc, int want_reply, const char *signame)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(
-        c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
-    put_stringz(pktout, signame);
-    pq_push(s->ppl.out_pq, pktout);
-
-    return TRUE;
-}
-
-static void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
-{
-    struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
-    struct ssh2_connection_state *s = c->connlayer;
-
-    PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
-    put_uint32(pktout, w);
-    put_uint32(pktout, h);
-    put_uint32(pktout, 0);	       /* pixel width */
-    put_uint32(pktout, 0);	       /* pixel height */
-    pq_push(s->ppl.out_pq, pktout);
-}
-
 static void ssh2channel_hint_channel_is_simple(SshChannel *sc)
 {
     struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
@@ -1681,45 +1326,6 @@ static SshChannel *ssh2_lportfwd_open(
 {
     struct ssh2_connection_state *s =
         container_of(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, description));
-
-    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 SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
-{
-    struct ssh2_connection_state *s =
-        container_of(cl, struct ssh2_connection_state, cl);
-    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
     struct ssh2_channel *c = snew(struct ssh2_channel);
     PktOut *pktout;
 
@@ -1728,101 +1334,12 @@ static SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
     c->halfopen = TRUE;
     c->chan = chan;
 
-    ppl_logevent(("Opening main session channel"));
-
-    pktout = ssh2_chanopen_init(c, "session");
+    pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi);
     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 =
-        container_of(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 =
-        container_of(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)
 {
@@ -1929,83 +1446,6 @@ static int ssh2_agent_forwarding_permitted(ConnectionLayer *cl)
     return conf_get_int(s->conf, CONF_agentfwd) && agent_exists();
 }
 
-static char *chan_open_x11(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc,
-    ptrlen peeraddr, int peerport)
-{
-    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
-    char *peeraddr_str;
-
-    ppl_logevent(("Received X11 connect request from %.*s:%d",
-                  PTRLEN_PRINTF(peeraddr), peerport));
-
-    if (!s->X11_fwd_enabled && !s->connshare)
-        return dupstr("X11 forwarding is not enabled");
-
-    peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
-    *ch = x11_new_channel(
-        s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
-    sfree(peeraddr_str);
-    ppl_logevent(("Opened X11 forward channel"));
-
-    return NULL;
-}
-
-static char *chan_open_forwarded_tcpip(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc,
-    ssh_sharing_connstate **share_ctx,
-    ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
-{
-    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
-    struct ssh_rportfwd pf, *realpf;
-    char *err;
-
-    ppl_logevent(("Received remote port %.*s:%d open request from %.*s:%d",
-                  PTRLEN_PRINTF(fwdaddr), fwdport,
-                  PTRLEN_PRINTF(peeraddr), peerport));
-
-    pf.shost = mkstr(fwdaddr);
-    pf.sport = fwdport;
-    realpf = find234(s->rportfwds, &pf, NULL);
-    sfree(pf.shost);
-
-    if (realpf == NULL)
-        return dupstr("Remote port is not recognised");
-
-    if (realpf->share_ctx) {
-        /*
-         * This port forwarding is on behalf of a connection-sharing
-         * downstream.
-         */
-        *share_ctx = realpf->share_ctx;
-        return NULL;
-    }
-
-    err = portfwdmgr_connect(
-        s->portfwdmgr, ch, realpf->dhost, realpf->dport,
-        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);
-        return dupstr("Port open failed");
-    }
-
-    ppl_logevent(("Forwarded port opened successfully"));
-    return NULL;
-}
-
-static char *chan_open_auth_agent(
-    struct ssh2_connection_state *s, Channel **ch, SshChannel *sc)
-{
-    if (!s->agent_fwd_enabled)
-        return dupstr("Agent forwarding is not enabled");
-
-    *ch = agentf_new(sc);
-    return NULL;
-}
-
 static int ssh2_connection_get_specials(
     PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
 {

+ 222 - 0
source/putty/ssh2connection.h

@@ -0,0 +1,222 @@
+#ifndef PUTTY_SSH2CONNECTION_H
+#define PUTTY_SSH2CONNECTION_H
+
+struct outstanding_channel_request;
+struct outstanding_global_request;
+
+struct ssh2_connection_state {
+    int crState;
+
+    Ssh *ssh;
+
+    ssh_sharing_state *connshare;
+    char *peer_verstring;
+
+    mainchan *mainchan;
+    SshChannel *mainchan_sc;
+    int ldisc_opts[LD_N_OPTIONS];
+    int session_attempt, session_status;
+    int term_width, term_height;
+    int want_user_input;
+
+    int ssh_is_simple;
+    int persistent;
+
+    Conf *conf;
+
+    tree234 *channels;		       /* indexed by local id */
+    int all_channels_throttled;
+
+    int X11_fwd_enabled;
+    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;
+};
+
+typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
+                                PktIn *pktin, void *ctx);
+void ssh2_queue_global_request_handler(
+    struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx);
+
+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 */
+};
+
+typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);
+
+void ssh2_channel_init(struct ssh2_channel *c);
+PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+                          cr_handler_fn_t handler, void *ctx);
+
+typedef enum ChanopenOutcome {
+    CHANOPEN_RESULT_FAILURE,
+    CHANOPEN_RESULT_SUCCESS,
+    CHANOPEN_RESULT_DOWNSTREAM,
+} ChanopenOutcome;
+
+typedef struct ChanopenResult {
+    ChanopenOutcome outcome;
+    union {
+        struct {
+            char *wire_message;        /* must be freed by recipient */
+            unsigned reason_code;
+        } failure;
+        struct {
+            Channel *channel;
+        } success;
+        struct {
+            ssh_sharing_connstate *share_ctx;
+        } downstream;
+    } u;
+} ChanopenResult;
+
+PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);
+
+PktOut *ssh2_portfwd_chanopen(
+    struct ssh2_connection_state *s, struct ssh2_channel *c,
+    const char *hostname, int port,
+    const char *description, const SocketPeerInfo *peerinfo);
+
+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);
+void ssh2_rportfwd_remove(
+    ConnectionLayer *cl, struct ssh_rportfwd *rpf);
+
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
+
+void ssh2channel_request_x11_forwarding(
+    SshChannel *c, int want_reply, const char *authproto,
+    const char *authdata, int screen_number, int oneshot);
+void ssh2channel_request_agent_forwarding(SshChannel *c, int want_reply);
+void ssh2channel_request_pty(
+    SshChannel *c, int want_reply, Conf *conf, int w, int h);
+int ssh2channel_send_env_var(
+    SshChannel *c, int want_reply, const char *var, const char *value);
+void ssh2channel_start_shell(SshChannel *c, int want_reply);
+void ssh2channel_start_command(
+    SshChannel *c, int want_reply, const char *command);
+int ssh2channel_start_subsystem(
+    SshChannel *c, int want_reply, const char *subsystem);
+int ssh2channel_send_env_var(
+    SshChannel *c, int want_reply, const char *var, const char *value);
+int ssh2channel_send_serial_break(
+    SshChannel *c, int want_reply, int length);
+int ssh2channel_send_signal(
+    SshChannel *c, int want_reply, const char *signame);
+void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h);
+
+#define CHANOPEN_RETURN_FAILURE(code, msgparams) do             \
+    {                                                           \
+        ChanopenResult toret;                                   \
+        toret.outcome = CHANOPEN_RESULT_FAILURE;                \
+        toret.u.failure.reason_code = code;                     \
+        toret.u.failure.wire_message = dupprintf msgparams;     \
+        return toret;                                           \
+    } while (0)
+
+#define CHANOPEN_RETURN_SUCCESS(chan) do                \
+    {                                                   \
+        ChanopenResult toret;                           \
+        toret.outcome = CHANOPEN_RESULT_SUCCESS;        \
+        toret.u.success.channel = chan;                 \
+        return toret;                                   \
+    } while (0)
+
+#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do            \
+    {                                                   \
+        ChanopenResult toret;                           \
+        toret.outcome = CHANOPEN_RESULT_DOWNSTREAM;     \
+        toret.u.downstream.share_ctx = shctx;           \
+        return toret;                                   \
+    } while (0)
+
+ChanopenResult ssh2_connection_parse_channel_open(
+    struct ssh2_connection_state *s, ptrlen type,
+    PktIn *pktin, SshChannel *sc);
+
+int ssh2_connection_parse_global_request(
+    struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);
+
+#endif /* PUTTY_SSH2CONNECTION_H */

+ 870 - 0
source/putty/ssh2kex-client.c

@@ -0,0 +1,870 @@
+/*
+ * Client side of key exchange 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"
+#include "ssh2transport.h"
+
+void ssh2kex_coroutine(struct ssh2_transport_state *s)
+{
+    PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+    PktIn *pktin;
+    PktOut *pktout;
+
+    crBegin(s->crStateKex);
+
+    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.
+         */
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        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);
+
+        seat_set_busy_status(s->ppl.seat, BUSY_WAITING);
+        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;
+        }
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        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. */
+        seat_set_busy_status(s->ppl.seat, 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;
+        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. */
+        seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+        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. */
+        seat_set_busy_status(s->ppl.seat, 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;
+        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;
+        }
+    }
+
+    ssh2transport_finalise_exhash(s);
+
+#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;
+
+    /* 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.
+         */
+        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_transient_hostkey_cache_add(s->thc, s->hkey);
+            } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) {
+                /*
+                 * 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_transient_hostkey_cache_add(s->thc, s->hkey);
+                s->need_gss_transient_hostkey = FALSE;
+            } else if (!ssh_transient_hostkey_cache_verify(s->thc, 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(ssh2_hostkey_algs); i++) {
+                    if (ssh2_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,
+                                ssh2_hostkey_algs[i].alg->ssh_id);
+                        else
+                            newlist = dupprintf(
+                                "%s", ssh2_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 = seat_verify_ssh_host_key(
+                    s->ppl.seat, 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;
+    }
+
+    crFinishV;
+}

File diff suppressed because it is too large
+ 52 - 1071
source/putty/ssh2transport.c


+ 219 - 0
source/putty/ssh2transport.h

@@ -0,0 +1,219 @@
+/*
+ * Header connecting the pieces of the SSH-2 transport layer.
+ */
+
+#ifndef PUTTY_SSH2TRANSPORT_H
+#define PUTTY_SSH2TRANSPORT_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;
+        struct {
+            const struct ssh_compression_alg *comp;
+            int delayed;
+        } comp;
+    } u;
+};
+
+#define HOSTKEY_ALGORITHMS(X)                   \
+    X(HK_ED25519, ssh_ecdsa_ed25519)            \
+    X(HK_ECDSA, ssh_ecdsa_nistp256)             \
+    X(HK_ECDSA, ssh_ecdsa_nistp384)             \
+    X(HK_ECDSA, ssh_ecdsa_nistp521)             \
+    X(HK_DSA, ssh_dss)                          \
+    X(HK_RSA, ssh_rsa)                          \
+    /* end of list */
+#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
+#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
+
+struct ssh_signkey_with_user_pref_id {
+    const ssh_keyalg *alg;
+    int id;
+};
+extern const struct ssh_signkey_with_user_pref_id
+    ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS];
+
+/*
+ * 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;
+
+typedef struct transport_direction {
+    const struct ssh2_cipheralg *cipher;
+    const struct ssh2_macalg *mac;
+    int etm_mode;
+    const struct ssh_compression_alg *comp;
+    int comp_delayed;
+} transport_direction;
+
+struct ssh2_transport_state {
+    int crState, crStateKex;
+
+    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 */
+#endif
+    ssh_transient_hostkey_cache *thc;
+
+    int gss_kex_used;
+
+    int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
+    Bignum p, g, e, f, K;
+    strbuf *client_kexinit, *server_kexinit;
+    int kex_init_value, kex_reply_value;
+    transport_direction 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 can_gssapi_keyex;
+    int need_gss_transient_hostkey;
+    int warned_about_no_gss_transient_hostkey;
+    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[N_HOSTKEY_ALGORITHMS];
+    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;
+};
+
+/* Helpers shared between transport and kex */
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s);
+void ssh2_transport_dialog_callback(void *, int);
+
+/* Provided by transport for use in kex */
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s);
+
+/* Provided by kex for use in transport */
+void ssh2kex_coroutine(struct ssh2_transport_state *s);
+
+#endif /* PUTTY_SSH2TRANSPORT_H */

+ 51 - 1
source/putty/sshcommon.c

@@ -857,7 +857,7 @@ int verify_ssh_manual_host_key(
 }
 
 /* ----------------------------------------------------------------------
- * Common get_specials function for the two SSH-1 layers.
+ * Common functions shared between SSH-1 layers.
  */
 
 int ssh1_common_get_specials(
@@ -876,6 +876,56 @@ int ssh1_common_get_specials(
     return FALSE;
 }
 
+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,
+                             "Remote side sent disconnect message:\n\"%.*s\"",
+                             PTRLEN_PRINTF(msg));
+            pq_pop(ppl->in_pq);
+            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;
+}
+
+void ssh1_compute_session_id(
+    unsigned char *session_id, const unsigned char *cookie,
+    struct RSAKey *hostkey, struct RSAKey *servkey)
+{
+    struct MD5Context md5c;
+    int i;
+
+    MD5Init(&md5c);
+    for (i = (bignum_bitcount(hostkey->modulus) + 7) / 8; i-- ;)
+        put_byte(&md5c, bignum_byte(hostkey->modulus, i));
+    for (i = (bignum_bitcount(servkey->modulus) + 7) / 8; i-- ;)
+        put_byte(&md5c, bignum_byte(servkey->modulus, i));
+    put_data(&md5c, cookie, 8);
+    MD5Final(session_id, &md5c);
+}
+
 /* ----------------------------------------------------------------------
  * Other miscellaneous utility functions.
  */

+ 6 - 0
source/putty/sshppl.h

@@ -140,4 +140,10 @@ 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 *);
 
+/* Other shared functions between ssh1 layers  */
+int ssh1_common_filter_queue(PacketProtocolLayer *ppl);
+void ssh1_compute_session_id(
+    unsigned char *session_id, const unsigned char *cookie,
+    struct RSAKey *hostkey, struct RSAKey *servkey);
+
 #endif /* PUTTY_SSHPPL_H */

Some files were not shown because too many files changed in this diff