浏览代码

Merge branch 'thirdparty_dev' into dev

# Conflicts:
#	source/putty/ssh.h

Source commit: 4f9419c3d0c09144e1577615f351ebaa5c6b7327
Martin Prikryl 6 年之前
父节点
当前提交
2e475fd631

+ 1 - 1
source/putty/agentf.c

@@ -164,7 +164,7 @@ Channel *agentf_new(SshChannel *c)
     af->c = c;
     af->chan.vt = &agentf_channelvt;
     af->chan.initial_fixed_window_size = 0;
-    af->rcvd_eof = TRUE;
+    af->rcvd_eof = FALSE;
     bufchain_init(&af->inbuffer);
     af->pending = NULL;
     af->input_wanted = TRUE;

+ 2 - 0
source/putty/defs.h

@@ -38,6 +38,8 @@ typedef uint32_t uint32;
 typedef struct BinarySink BinarySink;
 typedef struct BinarySource BinarySource;
 
+typedef struct IdempotentCallback IdempotentCallback;
+
 typedef struct SockAddr_tag *SockAddr;
 
 typedef struct Socket_vtable Socket_vtable;

+ 4 - 0
source/putty/misc.c

@@ -710,6 +710,7 @@ void bufchain_init(bufchain *ch)
 {
     ch->head = ch->tail = NULL;
     ch->buffersize = 0;
+    ch->ic = NULL;
 }
 
 void bufchain_clear(bufchain *ch)
@@ -761,6 +762,9 @@ void bufchain_add(bufchain *ch, const void *data, int len)
 	    ch->tail = newbuf;
 	}
     }
+
+    if (ch->ic)
+        queue_idempotent_callback(ch->ic);
 }
 
 void bufchain_consume(bufchain *ch, int len)

+ 1 - 0
source/putty/misc.h

@@ -78,6 +78,7 @@ struct bufchain_granule;
 struct bufchain_tag {
     struct bufchain_granule *head, *tail;
     int buffersize;		       /* current amount of buffered data */
+    IdempotentCallback *ic;
 };
 
 void bufchain_init(bufchain *ch);

+ 1 - 0
source/putty/putty.h

@@ -2,6 +2,7 @@
 #define PUTTY_PUTTY_H
 
 #include <stddef.h>		       /* for wchar_t */
+#include <limits.h>                    /* for INT_MAX */
 
 /*
  * Global variables. Most modules declare these `extern', but

+ 59 - 185
source/putty/ssh.c

@@ -79,7 +79,6 @@ static void ssh2_channel_check_close(struct ssh_channel *c);
 static void ssh_channel_close_local(struct ssh_channel *c, char const *reason);
 static void ssh_channel_destroy(struct ssh_channel *c);
 static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize);
-static void ssh2_msg_something_unimplemented(Ssh ssh, PktIn *pktin);
 static void ssh2_general_packet_processing(Ssh ssh, PktIn *pktin);
 static void ssh1_login_input(Ssh ssh);
 static void ssh2_userauth_input(Ssh ssh);
@@ -480,8 +479,7 @@ struct ssh_tag {
     int incoming_data_seen_eof;
     char *incoming_data_eof_message;
 
-    PktInQueue pq_full;
-    struct IdempotentCallback pq_full_consumer;
+    struct IdempotentCallback incoming_pkt_consumer;
 
     PktInQueue pq_ssh1_login;
     struct IdempotentCallback ssh1_login_icb;
@@ -735,22 +733,7 @@ static int s_write(Ssh ssh, const void *data, int len)
 
 static void ssh_pkt_write(Ssh ssh, PktOut *pkt)
 {
-    if (ssh->version == 2 && ssh->v2_cbc_ignore_workaround &&
-        bufchain_size(&ssh->outgoing_data) != 0) {
-        /*
-         * When using a CBC-mode cipher in SSH-2, it's necessary to
-         * ensure that an attacker can't provide data to be encrypted
-         * using an IV that they know. We ensure this by prefixing
-         * each packet that might contain user data with an
-         * SSH_MSG_IGNORE.
-         */
-	PktOut *ipkt = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_IGNORE);
-	put_stringz(ipkt, "");
-        ssh_bpp_format_packet(ssh->bpp, ipkt);
-    }
-
-    ssh_bpp_format_packet(ssh->bpp, pkt);
-    queue_idempotent_callback(&ssh->outgoing_data_sender);
+    pq_push(&ssh->bpp->out_pq, pkt);
 }
 
 /*
@@ -891,8 +874,6 @@ static void ssh2_add_sigblob(Ssh ssh, PktOut *pkt,
 
 static void ssh_feed_to_bpp(Ssh ssh)
 {
-    PacketQueueNode *prev_tail = ssh->pq_full.pqb.end.prev;
-
     assert(ssh->bpp);
     ssh_bpp_handle_input(ssh->bpp);
 
@@ -914,9 +895,6 @@ static void ssh_feed_to_bpp(Ssh ssh)
         ssh->close_expected = TRUE;
         ssh->disconnect_message_seen = TRUE;
     }
-
-    if (ssh->pq_full.pqb.end.prev != prev_tail)
-        queue_idempotent_callback(&ssh->pq_full_consumer);
 }
 
 static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
@@ -986,12 +964,14 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
     }
 
     ssh->bpp->out_raw = &ssh->outgoing_data;
+    ssh->bpp->out_raw->ic = &ssh->outgoing_data_sender;
     ssh->bpp->in_raw = &ssh->incoming_data;
-    ssh->bpp->in_pq = &ssh->pq_full;
+    ssh->bpp->in_pq.pqb.ic = &ssh->incoming_pkt_consumer;
     ssh->bpp->pls = &ssh->pls;
     ssh->bpp->logctx = ssh->logctx;
+    ssh->bpp->remote_bugs = ssh->remote_bugs;
 
-    queue_idempotent_callback(&ssh->incoming_data_consumer);
+    queue_idempotent_callback(&ssh->bpp->ic_in_raw);
     queue_idempotent_callback(&ssh->user_input_consumer);
 
     update_specials_menu(ssh->frontend);
@@ -1044,16 +1024,15 @@ static void ssh_process_incoming_data(void *ctx)
     }
 }
 
-static void ssh_process_pq_full(void *ctx)
+static void ssh_process_incoming_pkts(void *ctx)
 {
     Ssh ssh = (Ssh)ctx;
     PktIn *pktin;
 
-    while ((pktin = pq_pop(&ssh->pq_full)) != NULL) {
+    while ((pktin = pq_pop(&ssh->bpp->in_pq)) != NULL) {
         if (ssh->general_packet_processing)
             ssh->general_packet_processing(ssh, pktin);
 	ssh->packet_dispatch[pktin->type](ssh, pktin);
-	ssh_unref_packet(pktin);
     }
 }
 
@@ -1265,8 +1244,11 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port,
     ssh->connshare = NULL;
     ssh->attempting_connshare = TRUE;  /* affects socket logging behaviour */
     ssh->s = ssh_connection_sharing_init(
-        ssh->savedhost, ssh->savedport, ssh->conf, &ssh->cl, &ssh->plugvt,
-        &ssh->connshare);
+        ssh->savedhost, ssh->savedport, ssh->conf, ssh->frontend,
+        &ssh->plugvt, &ssh->connshare);
+    if (ssh->connshare)
+        ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl);
+
     ssh->attempting_connshare = FALSE;
     if (ssh->s != NULL) {
         /*
@@ -1461,78 +1443,14 @@ static void ssh_disconnect(Ssh ssh, const char *client_reason,
 	error = dupprintf("Disconnected: %s", client_reason);
     else
 	error = dupstr("Disconnected");
-    if (wire_reason) {
-	if (ssh->version == 1) {
-	    PktOut *pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_DISCONNECT);
-            put_stringz(pktout, wire_reason);
-            ssh_pkt_write(ssh, pktout);
-	} else if (ssh->version == 2) {
-	    PktOut *pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_DISCONNECT);
-	    put_uint32(pktout, code);
-	    put_stringz(pktout, wire_reason);
-	    put_stringz(pktout, "en");	/* language tag */
-	    ssh_pkt_write(ssh, pktout);
-	}
-    }
+    if (wire_reason)
+        ssh_bpp_queue_disconnect(ssh->bpp, wire_reason, code);
     ssh->close_expected = TRUE;
     ssh->clean_exit = clean_exit;
     ssh_closing(&ssh->plugvt, error, 0, 0);
     sfree(error);
 }
 
-int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint, ssh_key *key)
-{
-    if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) {
-        return -1;                     /* no manual keys configured */
-    }
-
-    if (fingerprint) {
-        /*
-         * The fingerprint string we've been given will have things
-         * like 'ssh-rsa 2048' at the front of it. Strip those off and
-         * narrow down to just the colon-separated hex block at the
-         * end of the string.
-         */
-        const char *p = strrchr(fingerprint, ' ');
-        fingerprint = p ? p+1 : fingerprint;
-        /* Quick sanity checks, including making sure it's in lowercase */
-        assert(strlen(fingerprint) == 16*3 - 1);
-        assert(fingerprint[2] == ':');
-        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
-
-        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
-                                 fingerprint))
-            return 1;                  /* success */
-    }
-
-    if (key) {
-        /*
-         * Construct the base64-encoded public key blob and see if
-         * that's listed.
-         */
-        strbuf *binblob;
-        char *base64blob;
-        int atoms, i;
-        binblob = strbuf_new();
-        ssh_key_public_blob(key, BinarySink_UPCAST(binblob));
-        atoms = (binblob->len + 2) / 3;
-        base64blob = snewn(atoms * 4 + 1, char);
-        for (i = 0; i < atoms; i++)
-            base64_encode_atom(binblob->u + 3*i,
-                               binblob->len - 3*i, base64blob + 4*i);
-        base64blob[atoms * 4] = '\0';
-        strbuf_free(binblob);
-        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
-                                 base64blob)) {
-            sfree(base64blob);
-            return 1;                  /* success */
-        }
-        sfree(base64blob);
-    }
-
-    return 0;
-}
-
 static void ssh1_coro_wrapper_initial(Ssh ssh, PktIn *pktin);
 static void ssh1_coro_wrapper_session(Ssh ssh, PktIn *pktin);
 static void ssh1_connection_input(Ssh ssh);
@@ -1666,7 +1584,7 @@ static void do_ssh1_login(void *vctx)
 	fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
 
         /* First check against manually configured host keys. */
-        s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL);
+        s->dlgret = verify_ssh_manual_host_key(ssh->conf, fingerprint, NULL);
         sfree(fingerprint);
         if (s->dlgret == 0) {          /* did not match */
             bombout(("Host key did not appear in manually configured list"));
@@ -1796,6 +1714,13 @@ static void do_ssh1_login(void *vctx)
 
     sfree(s->rsabuf);
 
+    /*
+     * Force the BPP to synchronously marshal all packets up to and
+     * including the SESSION_KEY into wire format, before we turn on
+     * crypto.
+     */
+    ssh_bpp_handle_output(ssh->bpp);
+
     {
         const struct ssh1_cipheralg *cipher =
             (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish :
@@ -3220,7 +3145,6 @@ static void do_ssh1_connection(void *vctx)
         pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_REQUEST_COMPRESSION);
         put_uint32(pkt, 6);            /* gzip compression level */
         ssh_pkt_write(ssh, pkt);
-        ssh1_bpp_requested_compression(ssh->bpp);
         crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL);
 	if (pktin->type != SSH1_SMSG_SUCCESS
 	    && pktin->type != SSH1_SMSG_FAILURE) {
@@ -3229,6 +3153,14 @@ static void do_ssh1_connection(void *vctx)
 	} else if (pktin->type == SSH1_SMSG_FAILURE) {
 	    c_write_str(ssh, "Server refused to compress\r\n");
 	} else {
+            /*
+             * We don't have to actually do anything here: the SSH-1
+             * BPP will take care of automatically starting the
+             * compression, by recognising our outgoing request packet
+             * and the success response. (Horrible, but it's the
+             * easiest way to avoid race conditions if other packets
+             * cross in transit.)
+             */
             logevent("Started zlib (RFC1950) compression");
         }
     }
@@ -3337,14 +3269,12 @@ static void ssh1_connection_input(Ssh ssh)
 
 static void ssh1_coro_wrapper_initial(Ssh ssh, PktIn *pktin)
 {
-    pktin->refcount++;   /* avoid packet being freed when we return */
     pq_push(&ssh->pq_ssh1_login, pktin);
     queue_idempotent_callback(&ssh->ssh1_login_icb);
 }
 
 static void ssh1_coro_wrapper_session(Ssh ssh, PktIn *pktin)
 {
-    pktin->refcount++;   /* avoid packet being freed when we return */
     pq_push(&ssh->pq_ssh1_connection, pktin);
     queue_idempotent_callback(&ssh->ssh1_connection_icb);
 }
@@ -5070,7 +5000,8 @@ static void do_ssh2_transport(void *vctx)
         logevent("Host key fingerprint is:");
         logevent(s->fingerprint);
         /* First check against manually configured host keys. */
-        s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint, s->hkey);
+        s->dlgret = verify_ssh_manual_host_key(ssh->conf,
+                                               s->fingerprint, s->hkey);
         if (s->dlgret == 0) {          /* did not match */
             bombout(("Host key did not appear in manually configured list"));
             crStopV;
@@ -5158,6 +5089,13 @@ static void do_ssh2_transport(void *vctx)
     ssh->stats.out.running = TRUE;
     ssh->stats.out.remaining = ssh->max_data_size;
 
+    /*
+     * Force the BPP to synchronously marshal all packets up to and
+     * including that NEWKEYS into wire format, before we switch over
+     * to new crypto.
+     */
+    ssh_bpp_handle_output(ssh->bpp);
+
     /*
      * We've sent client NEWKEYS, so create and initialise
      * client-to-server session keys.
@@ -6690,7 +6628,6 @@ static void ssh2_setup_env(struct ssh_channel *c, PktIn *pktin,
  */
 static void ssh2_msg_userauth(Ssh ssh, PktIn *pktin)
 {
-    pktin->refcount++;   /* avoid packet being freed when we return */
     pq_push(&ssh->pq_ssh2_userauth, pktin);
     if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
         /*
@@ -6698,10 +6635,10 @@ static void ssh2_msg_userauth(Ssh ssh, PktIn *pktin)
          * protocol has officially started, which means we must
          * install the dispatch-table entries for all the
          * connection-layer messages. In particular, we must do this
-         * _before_ we return to the loop in ssh_process_pq_full
+         * _before_ we return to the loop in ssh_process_incoming_pkts
          * that's processing the currently queued packets through the
          * dispatch table, because if (say) an SSH_MSG_GLOBAL_REQUEST
-         * is already pending in pq_full, we can't afford to delay
+         * is already pending in in_pq, we can't afford to delay
          * installing its dispatch table entry until after that queue
          * run is done.
          */
@@ -8226,7 +8163,6 @@ static void ssh2_userauth_input(Ssh ssh)
  */
 static void ssh2_msg_connection(Ssh ssh, PktIn *pktin)
 {
-    pktin->refcount++;   /* avoid packet being freed when we return */
     pq_push(&ssh->pq_ssh2_connection, pktin);
     queue_idempotent_callback(&ssh->ssh2_connection_icb);
 }
@@ -8475,14 +8411,15 @@ static void do_ssh2_connection(void *vctx)
 
     /*
      * Put our current pending packet queue back to the front of
-     * pq_full, and then schedule a callback to re-process those
+     * the main pq, and then schedule a callback to re-process those
      * packets (if any). That way, anything already in our queue that
      * matches any of the table entries we've just modified will go to
      * the right handler function, and won't come here to confuse us.
      */
     if (pq_peek(&ssh->pq_ssh2_connection)) {
-        pq_concatenate(&ssh->pq_full, &ssh->pq_ssh2_connection, &ssh->pq_full);
-        queue_idempotent_callback(&ssh->pq_full_consumer);
+        pq_concatenate(&ssh->bpp->in_pq,
+                       &ssh->pq_ssh2_connection, &ssh->bpp->in_pq);
+        queue_idempotent_callback(ssh->bpp->in_pq.pqb.ic);
     }
 
     /*
@@ -8706,7 +8643,6 @@ static void ssh2_msg_debug(Ssh ssh, PktIn *pktin)
 
 static void ssh2_msg_transport(Ssh ssh, PktIn *pktin)
 {
-    pktin->refcount++;   /* avoid packet being freed when we return */
     pq_push(&ssh->pq_ssh2_transport, pktin);
     queue_idempotent_callback(&ssh->ssh2_transport_icb);
 }
@@ -8725,18 +8661,6 @@ static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin)
     sfree(buf);
 }
 
-static void ssh2_msg_something_unimplemented(Ssh ssh, PktIn *pktin)
-{
-    PktOut *pktout;
-    pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_UNIMPLEMENTED);
-    put_uint32(pktout, pktin->sequence);
-    /*
-     * UNIMPLEMENTED messages MUST appear in the same order as the
-     * messages they respond to. Hence, never queue them.
-     */
-    ssh_pkt_write(ssh, pktout);
-}
-
 /*
  * Handle the top-level SSH-2 protocol.
  */
@@ -8775,20 +8699,17 @@ static void ssh2_protocol_setup(Ssh ssh)
 #endif
 
     /*
-     * Most messages cause SSH2_MSG_UNIMPLEMENTED.
+     * All message types we don't set explicitly will dispatch to
+     * ssh2_msg_unexpected.
      */
     for (i = 0; i < SSH_MAX_MSG; i++)
-	ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
+	ssh->packet_dispatch[i] = ssh2_msg_unexpected;
 
     /*
      * Initially, we only accept transport messages (and a few generic
      * ones). do_ssh2_userauth and do_ssh2_connection will each add
-     * more when they start. Messages that are understood but not
-     * currently acceptable go to ssh2_msg_unexpected.
+     * more when they start.
      */
-    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected;
     ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport;
     ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport;
     ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport;
@@ -8798,28 +8719,6 @@ static void ssh2_protocol_setup(Ssh ssh)
     ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport;
     ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport;
     ssh->packet_dispatch[SSH2_MSG_KEXGSS_GROUP] = ssh2_msg_transport;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected;
-    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */
-    /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */
-    ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
 
     /*
      * These messages have a special handler from the start.
@@ -8836,38 +8735,15 @@ static void ssh2_bare_connection_protocol_setup(Ssh ssh)
     ssh->bpp = ssh2_bare_bpp_new();
 
     /*
-     * Most messages cause SSH2_MSG_UNIMPLEMENTED.
-     */
-    for (i = 0; i < SSH_MAX_MSG; i++)
-	ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
-
-    /*
-     * Initially, we set all ssh-connection messages to 'unexpected';
-     * do_ssh2_connection will fill things in properly. We also handle
-     * a couple of messages from the transport protocol which aren't
-     * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG,
+     * Everything defaults to ssh2_msg_unexpected for the moment;
+     * do_ssh2_connection will fill things in properly. But we do
+     * handle a couple of messages from the transport protocol which
+     * aren't related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG,
      * DISCONNECT).
      */
-    ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
-    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
-
-    ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+    for (i = 0; i < SSH_MAX_MSG; i++)
+	ssh->packet_dispatch[i] = ssh2_msg_unexpected;
 
-    /*
-     * These messages have a special handler from the start.
-     */
     ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
     ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore;
     ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
@@ -9221,10 +9097,9 @@ static const char *ssh_init(Frontend *frontend, Backend **backend_handle,
     ssh->incoming_data_consumer.fn = ssh_process_incoming_data;
     ssh->incoming_data_consumer.ctx = ssh;
     ssh->incoming_data_consumer.queued = FALSE;
-    pq_in_init(&ssh->pq_full);
-    ssh->pq_full_consumer.fn = ssh_process_pq_full;
-    ssh->pq_full_consumer.ctx = ssh;
-    ssh->pq_full_consumer.queued = FALSE;
+    ssh->incoming_pkt_consumer.fn = ssh_process_incoming_pkts;
+    ssh->incoming_pkt_consumer.ctx = ssh;
+    ssh->incoming_pkt_consumer.queued = FALSE;
     pq_in_init(&ssh->pq_ssh1_login);
     ssh->ssh1_login_icb.fn = do_ssh1_login;
     ssh->ssh1_login_icb.ctx = ssh;
@@ -9407,7 +9282,6 @@ static void ssh_free(Backend *be)
     bufchain_clear(&ssh->incoming_data);
     bufchain_clear(&ssh->outgoing_data);
     sfree(ssh->incoming_data_eof_message);
-    pq_in_clear(&ssh->pq_full);
     pq_in_clear(&ssh->pq_ssh1_login);
     pq_in_clear(&ssh->pq_ssh1_connection);
     pq_in_clear(&ssh->pq_ssh2_transport);

+ 126 - 107
source/putty/ssh.h

@@ -56,10 +56,10 @@ struct ssh_channel;
 typedef struct PacketQueueNode PacketQueueNode;
 struct PacketQueueNode {
     PacketQueueNode *next, *prev;
+    int on_free_queue;     /* is this packet scheduled for freeing? */
 };
 
 typedef struct PktIn {
-    int refcount;
     int type;
     unsigned long sequence; /* SSH-2 incoming sequence number */
     PacketQueueNode qnode;  /* for linking this packet on to a queue */
@@ -88,6 +88,7 @@ typedef struct PktOut {
 
 typedef struct PacketQueueBase {
     PacketQueueNode end;
+    struct IdempotentCallback *ic;
 } PacketQueueBase;
 
 typedef struct PktInQueue {
@@ -160,12 +161,13 @@ int ssh2_censor_packet(
     ptrlen pkt, logblank_t *blanks);
 
 PktOut *ssh_new_packet(void);
-void ssh_unref_packet(PktIn *pkt);
 void ssh_free_pktout(PktOut *pkt);
 
 extern Socket ssh_connection_sharing_init(
-    const char *host, int port, Conf *conf, ConnectionLayer *cl,
+    const char *host, int port, Conf *conf, Frontend *frontend,
     Plug sshplug, ssh_sharing_state **state);
+void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
+                                     ConnectionLayer *cl);
 int ssh_share_test_for_upstream(const char *host, int port, Conf *conf);
 void share_got_pkt_from_server(ssh_sharing_connstate *ctx, int type,
                                const void *pkt, int pktlen);
@@ -1133,49 +1135,51 @@ int platform_ssh_share(const char *name, Conf *conf,
 void platform_ssh_share_cleanup(const char *name);
 
 /*
- * SSH-1 message type codes.
+ * List macro defining the SSH-1 message type codes.
  */
-#define SSH1_MSG_DISCONNECT                       1	/* 0x1 */
-#define SSH1_SMSG_PUBLIC_KEY                      2	/* 0x2 */
-#define SSH1_CMSG_SESSION_KEY                     3	/* 0x3 */
-#define SSH1_CMSG_USER                            4	/* 0x4 */
-#define SSH1_CMSG_AUTH_RSA                        6	/* 0x6 */
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7	/* 0x7 */
-#define SSH1_CMSG_AUTH_RSA_RESPONSE               8	/* 0x8 */
-#define SSH1_CMSG_AUTH_PASSWORD                   9	/* 0x9 */
-#define SSH1_CMSG_REQUEST_PTY                     10	/* 0xa */
-#define SSH1_CMSG_WINDOW_SIZE                     11	/* 0xb */
-#define SSH1_CMSG_EXEC_SHELL                      12	/* 0xc */
-#define SSH1_CMSG_EXEC_CMD                        13	/* 0xd */
-#define SSH1_SMSG_SUCCESS                         14	/* 0xe */
-#define SSH1_SMSG_FAILURE                         15	/* 0xf */
-#define SSH1_CMSG_STDIN_DATA                      16	/* 0x10 */
-#define SSH1_SMSG_STDOUT_DATA                     17	/* 0x11 */
-#define SSH1_SMSG_STDERR_DATA                     18	/* 0x12 */
-#define SSH1_CMSG_EOF                             19	/* 0x13 */
-#define SSH1_SMSG_EXIT_STATUS                     20	/* 0x14 */
-#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21	/* 0x15 */
-#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22	/* 0x16 */
-#define SSH1_MSG_CHANNEL_DATA                     23	/* 0x17 */
-#define SSH1_MSG_CHANNEL_CLOSE                    24	/* 0x18 */
-#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25	/* 0x19 */
-#define SSH1_SMSG_X11_OPEN                        27	/* 0x1b */
-#define SSH1_CMSG_PORT_FORWARD_REQUEST            28	/* 0x1c */
-#define SSH1_MSG_PORT_OPEN                        29	/* 0x1d */
-#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30	/* 0x1e */
-#define SSH1_SMSG_AGENT_OPEN                      31	/* 0x1f */
-#define SSH1_MSG_IGNORE                           32	/* 0x20 */
-#define SSH1_CMSG_EXIT_CONFIRMATION               33	/* 0x21 */
-#define SSH1_CMSG_X11_REQUEST_FORWARDING          34	/* 0x22 */
-#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35	/* 0x23 */
-#define SSH1_MSG_DEBUG                            36	/* 0x24 */
-#define SSH1_CMSG_REQUEST_COMPRESSION             37	/* 0x25 */
-#define SSH1_CMSG_AUTH_TIS                        39	/* 0x27 */
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40	/* 0x28 */
-#define SSH1_CMSG_AUTH_TIS_RESPONSE               41	/* 0x29 */
-#define SSH1_CMSG_AUTH_CCARD                      70	/* 0x46 */
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71	/* 0x47 */
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72	/* 0x48 */
+#define SSH1_MESSAGE_TYPES(X, y)                        \
+    X(y, SSH1_MSG_DISCONNECT, 1)                        \
+    X(y, SSH1_SMSG_PUBLIC_KEY, 2)                       \
+    X(y, SSH1_CMSG_SESSION_KEY, 3)                      \
+    X(y, SSH1_CMSG_USER, 4)                             \
+    X(y, SSH1_CMSG_AUTH_RSA, 6)                         \
+    X(y, SSH1_SMSG_AUTH_RSA_CHALLENGE, 7)               \
+    X(y, SSH1_CMSG_AUTH_RSA_RESPONSE, 8)                \
+    X(y, SSH1_CMSG_AUTH_PASSWORD, 9)                    \
+    X(y, SSH1_CMSG_REQUEST_PTY, 10)                     \
+    X(y, SSH1_CMSG_WINDOW_SIZE, 11)                     \
+    X(y, SSH1_CMSG_EXEC_SHELL, 12)                      \
+    X(y, SSH1_CMSG_EXEC_CMD, 13)                        \
+    X(y, SSH1_SMSG_SUCCESS, 14)                         \
+    X(y, SSH1_SMSG_FAILURE, 15)                         \
+    X(y, SSH1_CMSG_STDIN_DATA, 16)                      \
+    X(y, SSH1_SMSG_STDOUT_DATA, 17)                     \
+    X(y, SSH1_SMSG_STDERR_DATA, 18)                     \
+    X(y, SSH1_CMSG_EOF, 19)                             \
+    X(y, SSH1_SMSG_EXIT_STATUS, 20)                     \
+    X(y, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, 21)        \
+    X(y, SSH1_MSG_CHANNEL_OPEN_FAILURE, 22)             \
+    X(y, SSH1_MSG_CHANNEL_DATA, 23)                     \
+    X(y, SSH1_MSG_CHANNEL_CLOSE, 24)                    \
+    X(y, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, 25)       \
+    X(y, SSH1_SMSG_X11_OPEN, 27)                        \
+    X(y, SSH1_CMSG_PORT_FORWARD_REQUEST, 28)            \
+    X(y, SSH1_MSG_PORT_OPEN, 29)                        \
+    X(y, SSH1_CMSG_AGENT_REQUEST_FORWARDING, 30)        \
+    X(y, SSH1_SMSG_AGENT_OPEN, 31)                      \
+    X(y, SSH1_MSG_IGNORE, 32)                           \
+    X(y, SSH1_CMSG_EXIT_CONFIRMATION, 33)               \
+    X(y, SSH1_CMSG_X11_REQUEST_FORWARDING, 34)          \
+    X(y, SSH1_CMSG_AUTH_RHOSTS_RSA, 35)                 \
+    X(y, SSH1_MSG_DEBUG, 36)                            \
+    X(y, SSH1_CMSG_REQUEST_COMPRESSION, 37)             \
+    X(y, SSH1_CMSG_AUTH_TIS, 39)                        \
+    X(y, SSH1_SMSG_AUTH_TIS_CHALLENGE, 40)              \
+    X(y, SSH1_CMSG_AUTH_TIS_RESPONSE, 41)               \
+    X(y, SSH1_CMSG_AUTH_CCARD, 70)                      \
+    X(y, SSH1_SMSG_AUTH_CCARD_CHALLENGE, 71)            \
+    X(y, SSH1_CMSG_AUTH_CCARD_RESPONSE, 72)             \
+    /* end of list */
 
 #define SSH1_AUTH_RHOSTS                          1	/* 0x1 */
 #define SSH1_AUTH_RSA                             2	/* 0x2 */
@@ -1189,68 +1193,79 @@ void platform_ssh_share_cleanup(const char *name);
 #define SSH1_PROTOFLAGS_SUPPORTED                 0	/* 0x1 */
 
 /*
- * SSH-2 message type codes.
+ * List macro defining SSH-2 message type codes. Some of these depend
+ * on particular contexts (i.e. a previously negotiated kex or auth
+ * method)
  */
-#define SSH2_MSG_DISCONNECT                       1	/* 0x1 */
-#define SSH2_MSG_IGNORE                           2	/* 0x2 */
-#define SSH2_MSG_UNIMPLEMENTED                    3	/* 0x3 */
-#define SSH2_MSG_DEBUG                            4	/* 0x4 */
-#define SSH2_MSG_SERVICE_REQUEST                  5	/* 0x5 */
-#define SSH2_MSG_SERVICE_ACCEPT                   6	/* 0x6 */
-#define SSH2_MSG_KEXINIT                          20	/* 0x14 */
-#define SSH2_MSG_NEWKEYS                          21	/* 0x15 */
-#define SSH2_MSG_KEXDH_INIT                       30	/* 0x1e */
-#define SSH2_MSG_KEXDH_REPLY                      31	/* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD           30	/* 0x1e */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST               34	/* 0x22 */
-#define SSH2_MSG_KEX_DH_GEX_GROUP                 31	/* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_INIT                  32	/* 0x20 */
-#define SSH2_MSG_KEX_DH_GEX_REPLY                 33	/* 0x21 */
-#define SSH2_MSG_KEXGSS_INIT                      30	/* 0x1e */
-#define SSH2_MSG_KEXGSS_CONTINUE                  31	/* 0x1f */
-#define SSH2_MSG_KEXGSS_COMPLETE                  32	/* 0x20 */
-#define SSH2_MSG_KEXGSS_HOSTKEY                   33	/* 0x21 */
-#define SSH2_MSG_KEXGSS_ERROR                     34	/* 0x22 */
-#define SSH2_MSG_KEXGSS_GROUPREQ                  40	/* 0x28 */
-#define SSH2_MSG_KEXGSS_GROUP                     41	/* 0x29 */
-#define SSH2_MSG_KEXRSA_PUBKEY                    30    /* 0x1e */
-#define SSH2_MSG_KEXRSA_SECRET                    31    /* 0x1f */
-#define SSH2_MSG_KEXRSA_DONE                      32    /* 0x20 */
-#define SSH2_MSG_KEX_ECDH_INIT                    30    /* 0x1e */
-#define SSH2_MSG_KEX_ECDH_REPLY                   31    /* 0x1f */
-#define SSH2_MSG_KEX_ECMQV_INIT                   30    /* 0x1e */
-#define SSH2_MSG_KEX_ECMQV_REPLY                  31    /* 0x1f */
-#define SSH2_MSG_USERAUTH_REQUEST                 50	/* 0x32 */
-#define SSH2_MSG_USERAUTH_FAILURE                 51	/* 0x33 */
-#define SSH2_MSG_USERAUTH_SUCCESS                 52	/* 0x34 */
-#define SSH2_MSG_USERAUTH_BANNER                  53	/* 0x35 */
-#define SSH2_MSG_USERAUTH_PK_OK                   60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_REQUEST            60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61	/* 0x3d */
-#define SSH2_MSG_GLOBAL_REQUEST                   80	/* 0x50 */
-#define SSH2_MSG_REQUEST_SUCCESS                  81	/* 0x51 */
-#define SSH2_MSG_REQUEST_FAILURE                  82	/* 0x52 */
-#define SSH2_MSG_CHANNEL_OPEN                     90	/* 0x5a */
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91	/* 0x5b */
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92	/* 0x5c */
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93	/* 0x5d */
-#define SSH2_MSG_CHANNEL_DATA                     94	/* 0x5e */
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95	/* 0x5f */
-#define SSH2_MSG_CHANNEL_EOF                      96	/* 0x60 */
-#define SSH2_MSG_CHANNEL_CLOSE                    97	/* 0x61 */
-#define SSH2_MSG_CHANNEL_REQUEST                  98	/* 0x62 */
-#define SSH2_MSG_CHANNEL_SUCCESS                  99	/* 0x63 */
-#define SSH2_MSG_CHANNEL_FAILURE                  100	/* 0x64 */
-#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE               60
-#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN                  61
-#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE      63
-#define SSH2_MSG_USERAUTH_GSSAPI_ERROR                  64
-#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK                 65
-#define SSH2_MSG_USERAUTH_GSSAPI_MIC                    66
-
-/* Virtual packet type, for packets too short to even have a type */
-#define SSH_MSG_NO_TYPE_CODE                  0x100
+#define SSH2_MESSAGE_TYPES(X, K, A, y)                                  \
+    X(y, SSH2_MSG_DISCONNECT, 1)                                        \
+    X(y, SSH2_MSG_IGNORE, 2)                                            \
+    X(y, SSH2_MSG_UNIMPLEMENTED, 3)                                     \
+    X(y, SSH2_MSG_DEBUG, 4)                                             \
+    X(y, SSH2_MSG_SERVICE_REQUEST, 5)                                   \
+    X(y, SSH2_MSG_SERVICE_ACCEPT, 6)                                    \
+    X(y, SSH2_MSG_KEXINIT, 20)                                          \
+    X(y, SSH2_MSG_NEWKEYS, 21)                                          \
+    K(y, SSH2_MSG_KEXDH_INIT, 30, SSH2_PKTCTX_DHGROUP)                  \
+    K(y, SSH2_MSG_KEXDH_REPLY, 31, SSH2_PKTCTX_DHGROUP)                 \
+    K(y, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, 30, SSH2_PKTCTX_DHGEX)        \
+    K(y, SSH2_MSG_KEX_DH_GEX_REQUEST, 34, SSH2_PKTCTX_DHGEX)            \
+    K(y, SSH2_MSG_KEX_DH_GEX_GROUP, 31, SSH2_PKTCTX_DHGEX)              \
+    K(y, SSH2_MSG_KEX_DH_GEX_INIT, 32, SSH2_PKTCTX_DHGEX)               \
+    K(y, SSH2_MSG_KEX_DH_GEX_REPLY, 33, SSH2_PKTCTX_DHGEX)              \
+    K(y, SSH2_MSG_KEXGSS_INIT, 30, SSH2_PKTCTX_GSSKEX)                  \
+    K(y, SSH2_MSG_KEXGSS_CONTINUE, 31, SSH2_PKTCTX_GSSKEX)              \
+    K(y, SSH2_MSG_KEXGSS_COMPLETE, 32, SSH2_PKTCTX_GSSKEX)              \
+    K(y, SSH2_MSG_KEXGSS_HOSTKEY, 33, SSH2_PKTCTX_GSSKEX)               \
+    K(y, SSH2_MSG_KEXGSS_ERROR, 34, SSH2_PKTCTX_GSSKEX)                 \
+    K(y, SSH2_MSG_KEXGSS_GROUPREQ, 40, SSH2_PKTCTX_GSSKEX)              \
+    K(y, SSH2_MSG_KEXGSS_GROUP, 41, SSH2_PKTCTX_GSSKEX)                 \
+    K(y, SSH2_MSG_KEXRSA_PUBKEY, 30, SSH2_PKTCTX_RSAKEX)                \
+    K(y, SSH2_MSG_KEXRSA_SECRET, 31, SSH2_PKTCTX_RSAKEX)                \
+    K(y, SSH2_MSG_KEXRSA_DONE, 32, SSH2_PKTCTX_RSAKEX)                  \
+    K(y, SSH2_MSG_KEX_ECDH_INIT, 30, SSH2_PKTCTX_DHGEX)                 \
+    K(y, SSH2_MSG_KEX_ECDH_REPLY, 31, SSH2_PKTCTX_DHGEX)                \
+    X(y, SSH2_MSG_USERAUTH_REQUEST, 50)                                 \
+    X(y, SSH2_MSG_USERAUTH_FAILURE, 51)                                 \
+    X(y, SSH2_MSG_USERAUTH_SUCCESS, 52)                                 \
+    X(y, SSH2_MSG_USERAUTH_BANNER, 53)                                  \
+    A(y, SSH2_MSG_USERAUTH_PK_OK, 60, SSH2_PKTCTX_PUBLICKEY)            \
+    A(y, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, 60, SSH2_PKTCTX_PASSWORD)  \
+    A(y, SSH2_MSG_USERAUTH_INFO_REQUEST, 60, SSH2_PKTCTX_KBDINTER)      \
+    A(y, SSH2_MSG_USERAUTH_INFO_RESPONSE, 61, SSH2_PKTCTX_KBDINTER)     \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, 60, SSH2_PKTCTX_GSSAPI)     \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, 61, SSH2_PKTCTX_GSSAPI)        \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, 63, SSH2_PKTCTX_GSSAPI) \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_ERROR, 64, SSH2_PKTCTX_GSSAPI)        \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, 65, SSH2_PKTCTX_GSSAPI)       \
+    A(y, SSH2_MSG_USERAUTH_GSSAPI_MIC, 66, SSH2_PKTCTX_GSSAPI)          \
+    X(y, SSH2_MSG_GLOBAL_REQUEST, 80)                                   \
+    X(y, SSH2_MSG_REQUEST_SUCCESS, 81)                                  \
+    X(y, SSH2_MSG_REQUEST_FAILURE, 82)                                  \
+    X(y, SSH2_MSG_CHANNEL_OPEN, 90)                                     \
+    X(y, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, 91)                        \
+    X(y, SSH2_MSG_CHANNEL_OPEN_FAILURE, 92)                             \
+    X(y, SSH2_MSG_CHANNEL_WINDOW_ADJUST, 93)                            \
+    X(y, SSH2_MSG_CHANNEL_DATA, 94)                                     \
+    X(y, SSH2_MSG_CHANNEL_EXTENDED_DATA, 95)                            \
+    X(y, SSH2_MSG_CHANNEL_EOF, 96)                                      \
+    X(y, SSH2_MSG_CHANNEL_CLOSE, 97)                                    \
+    X(y, SSH2_MSG_CHANNEL_REQUEST, 98)                                  \
+    X(y, SSH2_MSG_CHANNEL_SUCCESS, 99)                                  \
+    X(y, SSH2_MSG_CHANNEL_FAILURE, 100)                                 \
+    /* end of list */
+
+#define DEF_ENUM_UNIVERSAL(y, name, value) name = value,
+#define DEF_ENUM_CONTEXTUAL(y, name, value, context) name = value,
+enum {
+    SSH1_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, y)
+    SSH2_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL,
+                       DEF_ENUM_CONTEXTUAL, DEF_ENUM_CONTEXTUAL, y)
+    /* Virtual packet type, for packets too short to even have a type */
+    SSH_MSG_NO_TYPE_CODE = 256
+};
+#undef DEF_ENUM_UNIVERSAL
+#undef DEF_ENUM_CONTEXTUAL
 
 /* Given that virtual packet types exist, this is how big the dispatch
  * table has to be */
@@ -1312,6 +1327,7 @@ void platform_ssh_share_cleanup(const char *name);
 
 const char *ssh1_pkt_type(int type);
 const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type);
+int ssh2_pkt_type_code_valid(unsigned type);
 
 /*
  * Need this to warn about support for the original SSH-2 keyfile
@@ -1363,4 +1379,7 @@ int first_in_commasep_string(char const *needle, char const *haystack,
 int in_commasep_string(char const *needle, char const *haystack, int haylen);
 void add_to_commasep(strbuf *buf, const char *data);
 
-#endif // WINSCP_VS
+int verify_ssh_manual_host_key(
+    Conf *conf, const char *fingerprint, ssh_key *key);
+
+#endif // WINSCP_VS

+ 89 - 35
source/putty/ssh1bpp.c

@@ -30,14 +30,17 @@ struct ssh1_bpp_state {
 
 static void ssh1_bpp_free(BinaryPacketProtocol *bpp);
 static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                                      const char *msg, int category);
 static PktOut *ssh1_bpp_new_pktout(int type);
-static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt);
 
 static const struct BinaryPacketProtocolVtable ssh1_bpp_vtable = {
     ssh1_bpp_free,
     ssh1_bpp_handle_input,
+    ssh1_bpp_handle_output,
     ssh1_bpp_new_pktout,
-    ssh1_bpp_format_packet,
+    ssh1_bpp_queue_disconnect,
 };
 
 BinaryPacketProtocol *ssh1_bpp_new(void)
@@ -45,6 +48,7 @@ BinaryPacketProtocol *ssh1_bpp_new(void)
     struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state);
     memset(s, 0, sizeof(*s));
     s->bpp.vt = &ssh1_bpp_vtable;
+    ssh_bpp_common_setup(&s->bpp);
     return &s->bpp;
 }
 
@@ -59,8 +63,7 @@ static void ssh1_bpp_free(BinaryPacketProtocol *bpp)
         ssh_decompressor_free(s->decompctx);
     if (s->crcda_ctx)
         crcda_free_context(s->crcda_ctx);
-    if (s->pktin)
-        ssh_unref_packet(s->pktin);
+    sfree(s->pktin);
     sfree(s);
 }
 
@@ -83,14 +86,11 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
     }
 }
 
-void ssh1_bpp_requested_compression(BinaryPacketProtocol *bpp)
-{
-    struct ssh1_bpp_state *s;
-    assert(bpp->vt == &ssh1_bpp_vtable);
-    s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
-
-    s->pending_compression_request = TRUE;
-}
+#define BPP_READ(ptr, len) do                                   \
+    {                                                           \
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+                              s->bpp.in_raw, ptr, len));        \
+    } while (0)
 
 static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
 {
@@ -104,8 +104,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
 
         {
             unsigned char lenbuf[4];
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  bpp->in_raw, lenbuf, 4));
+            BPP_READ(lenbuf, 4);
             s->len = toint(GET_32BIT_MSB_FIRST(lenbuf));
         }
 
@@ -125,14 +124,13 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
          */
         s->pktin = snew_plus(PktIn, s->biglen);
         s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
-        s->pktin->refcount = 1;
+        s->pktin->qnode.on_free_queue = FALSE;
         s->pktin->type = 0;
 
         s->maxlen = s->biglen;
         s->data = snew_plus_get_aux(s->pktin);
 
-        crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                              bpp->in_raw, s->data, s->biglen));
+        BPP_READ(s->data, s->biglen);
 
         if (s->cipher && detect_attack(s->crcda_ctx,
                                        s->data, s->biglen, NULL)) {
@@ -199,27 +197,45 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
                        NULL, 0, NULL);
         }
 
-        pq_push(s->bpp.in_pq, s->pktin);
+        pq_push(&s->bpp.in_pq, s->pktin);
 
         {
             int type = s->pktin->type;
             s->pktin = NULL;
 
-            if (type == SSH1_MSG_DISCONNECT)
+            switch (type) {
+              case SSH1_MSG_DISCONNECT:
                 s->bpp.seen_disconnect = TRUE;
-
-            if (type == SSH1_SMSG_SUCCESS && s->pending_compression_request) {
-                assert(!s->compctx);
-                assert(!s->decompctx);
-
-                s->compctx = ssh_compressor_new(&ssh_zlib);
-                s->decompctx = ssh_decompressor_new(&ssh_zlib);
-
-                s->pending_compression_request = FALSE;
-            }
-
-            if (type == SSH1_SMSG_FAILURE && s->pending_compression_request) {
-                s->pending_compression_request = FALSE;
+                break;
+
+              case SSH1_SMSG_SUCCESS:
+              case SSH1_SMSG_FAILURE:
+                if (s->pending_compression_request) {
+                    /*
+                     * This is the response to
+                     * SSH1_CMSG_REQUEST_COMPRESSION.
+                     */
+                    if (type == SSH1_SMSG_SUCCESS) {
+                        /*
+                         * If the response was positive, start
+                         * compression.
+                         */
+                        assert(!s->compctx);
+                        assert(!s->decompctx);
+
+                        s->compctx = ssh_compressor_new(&ssh_zlib);
+                        s->decompctx = ssh_decompressor_new(&ssh_zlib);
+                    }
+
+                    /*
+                     * Either way, cancel the pending flag, and
+                     * schedule a run of our output side in case we
+                     * had any packets queued up in the meantime.
+                     */
+                    s->pending_compression_request = FALSE;
+                    queue_idempotent_callback(&s->bpp.ic_out_pq);
+                }
+                break;
             }
         }
     }
@@ -238,9 +254,8 @@ static PktOut *ssh1_bpp_new_pktout(int pkt_type)
     return pkt;
 }
 
-static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt)
 {
-    struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
     int pad, biglen, i, pktoffs;
     unsigned long crc;
     int len;
@@ -287,8 +302,47 @@ static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
 
     bufchain_add(s->bpp.out_raw, pkt->data + pktoffs,
                  biglen + 4); /* len(length+padding+type+data+CRC) */
+}
 
-    ssh_free_pktout(pkt);
+static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+    struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp);
+    PktOut *pkt;
+
+    if (s->pending_compression_request) {
+        /*
+         * Don't send any output packets while we're awaiting a
+         * response to SSH1_CMSG_REQUEST_COMPRESSION, because if they
+         * cross over in transit with the responding SSH1_CMSG_SUCCESS
+         * then the other end could decode them with the wrong
+         * compression settings.
+         */
+        return;
+    }
+
+    while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+        int type = pkt->type;
+        ssh1_bpp_format_packet(s, pkt);
+        ssh_free_pktout(pkt);
+
+        if (type == SSH1_CMSG_REQUEST_COMPRESSION) {
+            /*
+             * When we see the actual compression request go past, set
+             * the pending flag, and stop processing packets this
+             * time.
+             */
+            s->pending_compression_request = TRUE;
+            break;
+        }
+    }
+}
+
+static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                                      const char *msg, int category)
+{
+    PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT);
+    put_stringz(pkt, msg);
+    pq_push(&bpp->out_pq, pkt);
 }
 
 #ifdef MPEXT

+ 34 - 15
source/putty/ssh2bpp-bare.c

@@ -22,14 +22,15 @@ struct ssh2_bare_bpp_state {
 
 static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp);
 static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh2_bare_bpp_new_pktout(int type);
-static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *);
 
 static const struct BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
     ssh2_bare_bpp_free,
     ssh2_bare_bpp_handle_input,
+    ssh2_bare_bpp_handle_output,
     ssh2_bare_bpp_new_pktout,
-    ssh2_bare_bpp_format_packet,
+    ssh2_bpp_queue_disconnect, /* in sshcommon.c */
 };
 
 BinaryPacketProtocol *ssh2_bare_bpp_new(void)
@@ -37,6 +38,7 @@ BinaryPacketProtocol *ssh2_bare_bpp_new(void)
     struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state);
     memset(s, 0, sizeof(*s));
     s->bpp.vt = &ssh2_bare_bpp_vtable;
+    ssh_bpp_common_setup(&s->bpp);
     return &s->bpp;
 }
 
@@ -44,11 +46,16 @@ static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
 {
     struct ssh2_bare_bpp_state *s =
         FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
-    if (s->pktin)
-        ssh_unref_packet(s->pktin);
+    sfree(s->pktin);
     sfree(s);
 }
 
+#define BPP_READ(ptr, len) do                                   \
+    {                                                           \
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+                              s->bpp.in_raw, ptr, len));        \
+    } while (0)
+
 static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
 {
     struct ssh2_bare_bpp_state *s =
@@ -60,8 +67,7 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
         /* Read the length field. */
         {
             unsigned char lenbuf[4];
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, lenbuf, 4));
+            BPP_READ(lenbuf, 4);
             s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
         }
 
@@ -75,8 +81,8 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
          */
         s->pktin = snew_plus(PktIn, s->packetlen);
         s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+        s->pktin->qnode.on_free_queue = FALSE;
         s->maxlen = 0;
-        s->pktin->refcount = 1;
         s->data = snew_plus_get_aux(s->pktin);
 
         s->pktin->sequence = s->incoming_sequence++;
@@ -84,8 +90,7 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
         /*
          * Read the remainder of the packet.
          */
-        crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                              s->bpp.in_raw, s->data, s->packetlen));
+        BPP_READ(s->data, s->packetlen);
 
         /*
          * The data we just read is precisely the initial type byte
@@ -111,7 +116,13 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
                        &s->pktin->sequence, 0, NULL);
         }
 
-        pq_push(s->bpp.in_pq, s->pktin);
+        if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+            sfree(s->pktin);
+            s->pktin = NULL;
+            continue;
+        }
+
+        pq_push(&s->bpp.in_pq, s->pktin);
 
         {
             int type = s->pktin->type;
@@ -133,11 +144,9 @@ static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type)
     return pkt;
 }
 
-static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s,
+                                        PktOut *pkt)
 {
-    struct ssh2_bare_bpp_state *s =
-        FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
-
     if (s->bpp.logctx) {
         ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5);
         logblank_t blanks[MAX_BLANKS];
@@ -155,6 +164,16 @@ static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
 
     PUT_32BIT(pkt->data, pkt->length - 4);
     bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bare_bpp_state *s =
+        FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp);
+    PktOut *pkt;
 
-    ssh_free_pktout(pkt);
+    while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+        ssh2_bare_bpp_format_packet(s, pkt);
+        ssh_free_pktout(pkt);
+    }
 }

+ 70 - 28
source/putty/ssh2bpp.c

@@ -25,6 +25,7 @@ struct ssh2_bpp_state {
     unsigned cipherblk;
     PktIn *pktin;
     struct DataTransferStats *stats;
+    int cbc_ignore_workaround;
 
     struct ssh2_bpp_direction in, out;
     /* comp and decomp logically belong in the per-direction
@@ -39,14 +40,15 @@ struct ssh2_bpp_state {
 
 static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
 static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh2_bpp_new_pktout(int type);
-static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt);
 
 static const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = {
     ssh2_bpp_free,
     ssh2_bpp_handle_input,
+    ssh2_bpp_handle_output,
     ssh2_bpp_new_pktout,
-    ssh2_bpp_format_packet,
+    ssh2_bpp_queue_disconnect, /* in sshcommon.c */
 };
 
 BinaryPacketProtocol *ssh2_bpp_new(struct DataTransferStats *stats)
@@ -55,6 +57,7 @@ BinaryPacketProtocol *ssh2_bpp_new(struct DataTransferStats *stats)
     memset(s, 0, sizeof(*s));
     s->bpp.vt = &ssh2_bpp_vtable;
     s->stats = stats;
+    ssh_bpp_common_setup(&s->bpp);
     return &s->bpp;
 }
 
@@ -74,8 +77,7 @@ static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
         ssh2_mac_free(s->in.mac);
     if (s->in_decomp)
         ssh_decompressor_free(s->in_decomp);
-    if (s->pktin)
-        ssh_unref_packet(s->pktin);
+    sfree(s->pktin);
     sfree(s);
 }
 
@@ -100,8 +102,13 @@ void ssh2_bpp_new_outgoing_crypto(
         s->out.cipher = ssh2_cipher_new(cipher);
         ssh2_cipher_setkey(s->out.cipher, ckey);
         ssh2_cipher_setiv(s->out.cipher, iv);
+
+        s->cbc_ignore_workaround = (
+            (ssh2_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+            !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
     } else {
         s->out.cipher = NULL;
+        s->cbc_ignore_workaround = FALSE;
     }
     s->out.etm_mode = etm_mode;
     if (mac) {
@@ -159,6 +166,12 @@ void ssh2_bpp_new_incoming_crypto(
     s->pending_newkeys = FALSE;
 }
 
+#define BPP_READ(ptr, len) do                                   \
+    {                                                           \
+        crMaybeWaitUntilV(bufchain_try_fetch_consume(           \
+                              s->bpp.in_raw, ptr, len));        \
+    } while (0)
+
 static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
 {
     struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
@@ -207,8 +220,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
             }
 
             /* Read an amount corresponding to the MAC. */
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, s->buf, s->maclen));
+            BPP_READ(s->buf, s->maclen);
 
             s->packetlen = 0;
             ssh2_mac_start(s->in.mac);
@@ -217,10 +229,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
             for (;;) { /* Once around this loop per cipher block. */
                 /* Read another cipher-block's worth, and tack it on to
                  * the end. */
-                crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                      s->bpp.in_raw,
-                                      s->buf + (s->packetlen + s->maclen),
-                                      s->cipherblk));
+                BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
                 /* Decrypt one more block (a little further back in
                  * the stream). */
                 ssh2_cipher_decrypt(s->in.cipher,
@@ -249,8 +258,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             s->pktin = snew_plus(PktIn, s->maxlen);
             s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
-            s->pktin->refcount = 1;
             s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = FALSE;
             s->data = snew_plus_get_aux(s->pktin);
             memcpy(s->data, s->buf, s->maxlen);
         } else if (s->in.mac && s->in.etm_mode) {
@@ -263,8 +272,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              * OpenSSH encrypt-then-MAC mode: the packet length is
              * unencrypted, unless the cipher supports length encryption.
              */
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, s->buf, 4));
+            BPP_READ(s->buf, 4);
 
             /* Cipher supports length decryption, so do it */
             if (s->in.cipher && (ssh2_cipher_alg(s->in.cipher)->flags &
@@ -300,17 +308,15 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              */
             s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
             s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
-            s->pktin->refcount = 1;
             s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = FALSE;
             s->data = snew_plus_get_aux(s->pktin);
             memcpy(s->data, s->buf, 4);
 
             /*
              * Read the remainder of the packet.
              */
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, s->data + 4,
-                                  s->packetlen + s->maclen - 4));
+            BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
 
             /*
              * Check the MAC.
@@ -335,8 +341,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
              * Acquire and decrypt the first block of the packet. This will
              * contain the length and padding details.
              */
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, s->buf, s->cipherblk));
+            BPP_READ(s->buf, s->cipherblk);
 
             if (s->in.cipher)
                 ssh2_cipher_decrypt(
@@ -369,17 +374,16 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
             s->maxlen = s->packetlen + s->maclen;
             s->pktin = snew_plus(PktIn, s->maxlen);
             s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
-            s->pktin->refcount = 1;
             s->pktin->type = 0;
+            s->pktin->qnode.on_free_queue = FALSE;
             s->data = snew_plus_get_aux(s->pktin);
             memcpy(s->data, s->buf, s->cipherblk);
 
             /*
              * Read and decrypt the remainder of the packet.
              */
-            crMaybeWaitUntilV(bufchain_try_fetch_consume(
-                                  s->bpp.in_raw, s->data + s->cipherblk,
-                                  s->packetlen + s->maclen - s->cipherblk));
+            BPP_READ(s->data + s->cipherblk,
+                     s->packetlen + s->maclen - s->cipherblk);
 
             /* Decrypt everything _except_ the MAC. */
             if (s->in.cipher)
@@ -476,7 +480,13 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
                        &s->pktin->sequence, 0, NULL);
         }
 
-        pq_push(s->bpp.in_pq, s->pktin);
+        if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+            sfree(s->pktin);
+            s->pktin = NULL;
+            continue;
+        }
+
+        pq_push(&s->bpp.in_pq, s->pktin);
 
         {
             int type = s->pktin->type;
@@ -609,10 +619,8 @@ static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
 
 }
 
-static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
+static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
 {
-    struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
-
     if (pkt->minlen > 0 && !s->out_comp) {
         /*
          * If we've been told to pad the packet out to a given minimum
@@ -676,8 +684,42 @@ static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt)
 
     ssh2_bpp_format_packet_inner(s, pkt);
     bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
 
-    ssh_free_pktout(pkt);
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+    struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp);
+    PktOut *pkt;
+
+    if (s->cbc_ignore_workaround) {
+        /*
+         * When using a CBC-mode cipher in SSH-2, it's necessary to
+         * ensure that an attacker can't provide data to be encrypted
+         * using an IV that they know. We ensure this by inserting an
+         * SSH_MSG_IGNORE if the last cipher block of the previous
+         * packet has already been sent to the network (which we
+         * approximate conservatively by checking if it's vanished
+         * from out_raw).
+         */
+        if (bufchain_size(s->bpp.out_raw) <
+            (ssh2_cipher_alg(s->out.cipher)->blksize +
+             ssh2_mac_alg(s->out.mac)->len)) {
+            /*
+             * There's less data in out_raw than the MAC size plus the
+             * cipher block size, which means at least one byte of
+             * that cipher block must already have left. Add an
+             * IGNORE.
+             */
+            pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
+            put_stringz(pkt, "");
+            ssh2_bpp_format_packet(s, pkt);
+        }
+    }
+
+    while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+        ssh2_bpp_format_packet(s, pkt);
+        ssh_free_pktout(pkt);
+    }
 }
 
 #ifdef MPEXT

+ 33 - 6
source/putty/sshbpp.h

@@ -5,30 +5,48 @@
 #ifndef PUTTY_SSHBPP_H
 #define PUTTY_SSHBPP_H
 
-typedef struct BinaryPacketProtocol BinaryPacketProtocol;
-
 struct BinaryPacketProtocolVtable {
     void (*free)(BinaryPacketProtocol *); 
     void (*handle_input)(BinaryPacketProtocol *);
+    void (*handle_output)(BinaryPacketProtocol *);
     PktOut *(*new_pktout)(int type);
-    void (*format_packet)(BinaryPacketProtocol *, PktOut *);
+    void (*queue_disconnect)(BinaryPacketProtocol *,
+                             const char *msg, int category);
 };
 
 struct BinaryPacketProtocol {
     const struct BinaryPacketProtocolVtable *vt;
     bufchain *in_raw, *out_raw;
-    PktInQueue *in_pq;
+    PktInQueue in_pq;
+    PktOutQueue out_pq;
     PacketLogSettings *pls;
     LogContext *logctx;
+    Ssh ssh;
+
+    /* ic_in_raw is filled in by the BPP (probably by calling
+     * ssh_bpp_common_setup). The BPP's owner triggers it when data is
+     * added to in_raw, and also when the BPP is newly created. */
+    IdempotentCallback ic_in_raw;
+
+    /* ic_out_pq is entirely internal to the BPP itself; it's used as
+     * the callback on out_pq. */
+    IdempotentCallback ic_out_pq;
+
+    int remote_bugs;
 
     int seen_disconnect;
     char *error;
 };
 
-#define ssh_bpp_free(bpp) ((bpp)->vt->free(bpp))
 #define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp))
+#define ssh_bpp_handle_output(bpp) ((bpp)->vt->handle_output(bpp))
 #define ssh_bpp_new_pktout(bpp, type) ((bpp)->vt->new_pktout(type))
-#define ssh_bpp_format_packet(bpp, pkt) ((bpp)->vt->format_packet(bpp, pkt))
+#define ssh_bpp_queue_disconnect(bpp, msg, cat) \
+    ((bpp)->vt->queue_disconnect(bpp, msg, cat))
+
+/* ssh_bpp_free is more than just a macro wrapper on the vtable; it
+ * does centralised parts of the freeing too. */
+void ssh_bpp_free(BinaryPacketProtocol *bpp);
 
 BinaryPacketProtocol *ssh1_bpp_new(void);
 void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
@@ -40,6 +58,15 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
  * up zlib compression if it was SUCCESS. */
 void ssh1_bpp_requested_compression(BinaryPacketProtocol *bpp);
 
+/* Helper routine which does common BPP initialisation, e.g. setting
+ * up in_pq and out_pq, and initialising input_consumer. */
+void ssh_bpp_common_setup(BinaryPacketProtocol *);
+
+/* Common helper functions between the SSH-2 full and bare BPPs */
+void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                               const char *msg, int category);
+int ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin);
+
 /*
  * Structure that tracks how much data is sent and received, for
  * purposes of triggering an SSH-2 rekey when either one gets over a

+ 209 - 114
source/putty/sshcommon.c

@@ -8,32 +8,68 @@
 
 #include "putty.h"
 #include "ssh.h"
+#include "sshbpp.h"
 #include "sshchan.h"
 
 /* ----------------------------------------------------------------------
  * Implementation of PacketQueue.
  */
 
+static void pq_ensure_unlinked(PacketQueueNode *node)
+{
+    if (node->on_free_queue) {
+        node->next->prev = node->prev;
+        node->prev->next = node->next;
+    } else {
+        assert(!node->next);
+        assert(!node->prev);
+    }
+}
+
 void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
 {
-    assert(!node->next);
-    assert(!node->prev);
+    pq_ensure_unlinked(node);
     node->next = &pqb->end;
     node->prev = pqb->end.prev;
     node->next->prev = node;
     node->prev->next = node;
+
+    if (pqb->ic)
+        queue_idempotent_callback(pqb->ic);
 }
 
 void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
 {
-    assert(!node->next);
-    assert(!node->prev);
+    pq_ensure_unlinked(node);
     node->prev = &pqb->end;
     node->next = pqb->end.next;
     node->next->prev = node;
     node->prev->next = node;
+
+    if (pqb->ic)
+        queue_idempotent_callback(pqb->ic);
 }
 
+static PacketQueueNode pktin_freeq_head = {
+    &pktin_freeq_head, &pktin_freeq_head, TRUE
+};
+
+static void pktin_free_queue_callback(void *vctx)
+{
+    while (pktin_freeq_head.next != &pktin_freeq_head) {
+        PacketQueueNode *node = pktin_freeq_head.next;
+        PktIn *pktin = FROMFIELD(node, PktIn, qnode);
+        pktin_freeq_head.next = node->next;
+        sfree(pktin);
+    }
+
+    pktin_freeq_head.prev = &pktin_freeq_head;
+}
+
+static IdempotentCallback ic_pktin_free = {
+    pktin_free_queue_callback, NULL, FALSE
+};
+
 static PktIn *pq_in_get(PacketQueueBase *pqb, int pop)
 {
     PacketQueueNode *node = pqb->end.next;
@@ -43,7 +79,13 @@ static PktIn *pq_in_get(PacketQueueBase *pqb, int pop)
     if (pop) {
         node->next->prev = node->prev;
         node->prev->next = node->next;
-        node->prev = node->next = NULL;
+
+        node->prev = pktin_freeq_head.prev;
+        node->next = &pktin_freeq_head;
+        node->next->prev = node;
+        node->prev->next = node;
+        node->on_free_queue = TRUE;
+        queue_idempotent_callback(&ic_pktin_free);
     }
 
     return FROMFIELD(node, PktIn, qnode);
@@ -66,12 +108,14 @@ static PktOut *pq_out_get(PacketQueueBase *pqb, int pop)
 
 void pq_in_init(PktInQueue *pq)
 {
+    pq->pqb.ic = NULL;
     pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
     pq->get = pq_in_get;
 }
 
 void pq_out_init(PktOutQueue *pq)
 {
+    pq->pqb.ic = NULL;
     pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
     pq->get = pq_out_get;
 }
@@ -79,13 +123,18 @@ void pq_out_init(PktOutQueue *pq)
 void pq_in_clear(PktInQueue *pq)
 {
     PktIn *pkt;
-    while ((pkt = pq_pop(pq)) != NULL)
-        ssh_unref_packet(pkt);
+    pq->pqb.ic = NULL;
+    while ((pkt = pq_pop(pq)) != NULL) {
+        /* No need to actually free these packets: pq_pop on a
+         * PktInQueue will automatically move them to the free
+         * queue. */
+    }
 }
 
 void pq_out_clear(PktOutQueue *pq)
 {
     PktOut *pkt;
+    pq->pqb.ic = NULL;
     while ((pkt = pq_pop(pq)) != NULL)
         ssh_free_pktout(pkt);
 }
@@ -149,6 +198,9 @@ void pq_base_concatenate(PacketQueueBase *qdest,
         qdest->end.prev = tail2;
         head1->prev = &qdest->end;
         tail2->next = &qdest->end;
+
+        if (qdest->ic)
+            queue_idempotent_callback(qdest->ic);
     }
 }
 
@@ -169,6 +221,7 @@ PktOut *ssh_new_packet(void)
     pkt->downstream_id = 0;
     pkt->additional_log_text = NULL;
     pkt->qnode.next = pkt->qnode.prev = NULL;
+    pkt->qnode.on_free_queue = FALSE;
 
     return pkt;
 }
@@ -194,17 +247,12 @@ static void ssh_pkt_BinarySink_write(BinarySink *bs,
     ssh_pkt_adddata(pkt, data, len);
 }
 
-void ssh_unref_packet(PktIn *pkt)
-{
-    if (--pkt->refcount <= 0)
-        sfree(pkt);
-}
-
 void ssh_free_pktout(PktOut *pkt)
 {
     sfree(pkt->data);
     sfree(pkt);
 }
+
 /* ----------------------------------------------------------------------
  * Implement zombiechan_new() and its trivial vtable.
  */
@@ -560,112 +608,159 @@ void add_to_commasep(strbuf *buf, const char *data)
  * string names.
  */
 
-#define translate(x) if (type == x) return #x
-#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x
-#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x
+#define TRANSLATE_UNIVERSAL(y, name, value)      \
+    if (type == value) return #name;
+#define TRANSLATE_KEX(y, name, value, ctx) \
+    if (type == value && pkt_kctx == ctx) return #name;
+#define TRANSLATE_AUTH(y, name, value, ctx) \
+    if (type == value && pkt_actx == ctx) return #name;
+
 const char *ssh1_pkt_type(int type)
 {
-    translate(SSH1_MSG_DISCONNECT);
-    translate(SSH1_SMSG_PUBLIC_KEY);
-    translate(SSH1_CMSG_SESSION_KEY);
-    translate(SSH1_CMSG_USER);
-    translate(SSH1_CMSG_AUTH_RSA);
-    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
-    translate(SSH1_CMSG_AUTH_PASSWORD);
-    translate(SSH1_CMSG_REQUEST_PTY);
-    translate(SSH1_CMSG_WINDOW_SIZE);
-    translate(SSH1_CMSG_EXEC_SHELL);
-    translate(SSH1_CMSG_EXEC_CMD);
-    translate(SSH1_SMSG_SUCCESS);
-    translate(SSH1_SMSG_FAILURE);
-    translate(SSH1_CMSG_STDIN_DATA);
-    translate(SSH1_SMSG_STDOUT_DATA);
-    translate(SSH1_SMSG_STDERR_DATA);
-    translate(SSH1_CMSG_EOF);
-    translate(SSH1_SMSG_EXIT_STATUS);
-    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
-    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
-    translate(SSH1_MSG_CHANNEL_DATA);
-    translate(SSH1_MSG_CHANNEL_CLOSE);
-    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
-    translate(SSH1_SMSG_X11_OPEN);
-    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
-    translate(SSH1_MSG_PORT_OPEN);
-    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
-    translate(SSH1_SMSG_AGENT_OPEN);
-    translate(SSH1_MSG_IGNORE);
-    translate(SSH1_CMSG_EXIT_CONFIRMATION);
-    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
-    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
-    translate(SSH1_MSG_DEBUG);
-    translate(SSH1_CMSG_REQUEST_COMPRESSION);
-    translate(SSH1_CMSG_AUTH_TIS);
-    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
-    translate(SSH1_CMSG_AUTH_CCARD);
-    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+    SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y);
     return "unknown";
 }
 const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
 {
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI);
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI);
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI);
-    translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI);
-    translate(SSH2_MSG_DISCONNECT);
-    translate(SSH2_MSG_IGNORE);
-    translate(SSH2_MSG_UNIMPLEMENTED);
-    translate(SSH2_MSG_DEBUG);
-    translate(SSH2_MSG_SERVICE_REQUEST);
-    translate(SSH2_MSG_SERVICE_ACCEPT);
-    translate(SSH2_MSG_KEXINIT);
-    translate(SSH2_MSG_NEWKEYS);
-    translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
-    translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
-    translatek(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, SSH2_PKTCTX_DHGEX);
-    translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
-    translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
-    translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
-    translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
-    translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
-    translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
-    translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
-    translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX);
-    translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX);
-    translatek(SSH2_MSG_KEXGSS_INIT, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_CONTINUE, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_COMPLETE, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_HOSTKEY, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_ERROR, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_GROUPREQ, SSH2_PKTCTX_GSSKEX);
-    translatek(SSH2_MSG_KEXGSS_GROUP, SSH2_PKTCTX_GSSKEX);
-    translate(SSH2_MSG_USERAUTH_REQUEST);
-    translate(SSH2_MSG_USERAUTH_FAILURE);
-    translate(SSH2_MSG_USERAUTH_SUCCESS);
-    translate(SSH2_MSG_USERAUTH_BANNER);
-    translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
-    translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
-    translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
-    translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
-    translate(SSH2_MSG_GLOBAL_REQUEST);
-    translate(SSH2_MSG_REQUEST_SUCCESS);
-    translate(SSH2_MSG_REQUEST_FAILURE);
-    translate(SSH2_MSG_CHANNEL_OPEN);
-    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
-    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
-    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-    translate(SSH2_MSG_CHANNEL_DATA);
-    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
-    translate(SSH2_MSG_CHANNEL_EOF);
-    translate(SSH2_MSG_CHANNEL_CLOSE);
-    translate(SSH2_MSG_CHANNEL_REQUEST);
-    translate(SSH2_MSG_CHANNEL_SUCCESS);
-    translate(SSH2_MSG_CHANNEL_FAILURE);
+    SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y);
     return "unknown";
 }
-#undef translate
-#undef translatec
+
+#undef TRANSLATE_UNIVERSAL
+#undef TRANSLATE_KEX
+#undef TRANSLATE_AUTH
+
+/* ----------------------------------------------------------------------
+ * Common helper functions for clients and implementations of
+ * BinaryPacketProtocol.
+ */
+
+static void ssh_bpp_input_raw_data_callback(void *context)
+{
+    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+    ssh_bpp_handle_input(bpp);
+}
+
+static void ssh_bpp_output_packet_callback(void *context)
+{
+    BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+    ssh_bpp_handle_output(bpp);
+}
+
+void ssh_bpp_common_setup(BinaryPacketProtocol *bpp)
+{
+    pq_in_init(&bpp->in_pq);
+    pq_out_init(&bpp->out_pq);
+    bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;
+    bpp->ic_in_raw.ctx = bpp;
+    bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback;
+    bpp->ic_out_pq.ctx = bpp;
+    bpp->out_pq.pqb.ic = &bpp->ic_out_pq;
+}
+
+void ssh_bpp_free(BinaryPacketProtocol *bpp)
+{
+    delete_callbacks_for_context(bpp);
+    bpp->vt->free(bpp);
+}
+
+void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+                               const char *msg, int category)
+{
+    PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT);
+    put_uint32(pkt, category);
+    put_stringz(pkt, msg);
+    put_stringz(pkt, "en");            /* language tag */
+    pq_push(&bpp->out_pq, pkt);
+}
+
+#define BITMAP_UNIVERSAL(y, name, value)         \
+    | (value >= y && value < y+32 ? 1UL << (value-y) : 0)
+#define BITMAP_CONDITIONAL(y, name, value, ctx) \
+    BITMAP_UNIVERSAL(y, name, value)
+#define SSH2_BITMAP_WORD(y) \
+    (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \
+                          BITMAP_CONDITIONAL, (32*y)))
+
+int ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
+{
+    static const unsigned valid_bitmap[] = {
+        SSH2_BITMAP_WORD(0),
+        SSH2_BITMAP_WORD(1),
+        SSH2_BITMAP_WORD(2),
+        SSH2_BITMAP_WORD(3),
+        SSH2_BITMAP_WORD(4),
+        SSH2_BITMAP_WORD(5),
+        SSH2_BITMAP_WORD(6),
+        SSH2_BITMAP_WORD(7),
+    };
+
+    if (pktin->type < 0x100 &&
+        !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) {
+        PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED);
+        put_uint32(pkt, pktin->sequence);
+        pq_push(&bpp->out_pq, pkt);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+#undef BITMAP_UNIVERSAL
+#undef BITMAP_CONDITIONAL
+#undef SSH1_BITMAP_WORD
+
+/* ----------------------------------------------------------------------
+ * Function to check a host key against any manually configured in Conf.
+ */
+
+int verify_ssh_manual_host_key(
+    Conf *conf, const char *fingerprint, ssh_key *key)
+{
+    if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0))
+        return -1;                     /* no manual keys configured */
+
+    if (fingerprint) {
+        /*
+         * The fingerprint string we've been given will have things
+         * like 'ssh-rsa 2048' at the front of it. Strip those off and
+         * narrow down to just the colon-separated hex block at the
+         * end of the string.
+         */
+        const char *p = strrchr(fingerprint, ' ');
+        fingerprint = p ? p+1 : fingerprint;
+        /* Quick sanity checks, including making sure it's in lowercase */
+        assert(strlen(fingerprint) == 16*3 - 1);
+        assert(fingerprint[2] == ':');
+        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
+
+        if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint))
+            return 1;                  /* success */
+    }
+
+    if (key) {
+        /*
+         * Construct the base64-encoded public key blob and see if
+         * that's listed.
+         */
+        strbuf *binblob;
+        char *base64blob;
+        int atoms, i;
+        binblob = strbuf_new();
+        ssh_key_public_blob(key, BinarySink_UPCAST(binblob));
+        atoms = (binblob->len + 2) / 3;
+        base64blob = snewn(atoms * 4 + 1, char);
+        for (i = 0; i < atoms; i++)
+            base64_encode_atom(binblob->u + 3*i,
+                               binblob->len - 3*i, base64blob + 4*i);
+        base64blob[atoms * 4] = '\0';
+        strbuf_free(binblob);
+        if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) {
+            sfree(base64blob);
+            return 1;                  /* success */
+        }
+        sfree(base64blob);
+    }
+
+    return 0;
+}

+ 35 - 4
source/putty/sshcr.h

@@ -46,9 +46,40 @@
         } while (0)
 #define crStop(z)       do{ *crLine = 0; return (z); }while(0)
 #define crStopV         do{ *crLine = 0; return; }while(0)
-#define crWaitUntil(c)  do { crReturn(0); } while (!(c))
-#define crWaitUntilV(c) do { crReturnV; } while (!(c))
-#define crMaybeWaitUntil(c) do { while (!(c)) crReturn(0); } while (0)
-#define crMaybeWaitUntilV(c) do { while (!(c)) crReturnV; } while (0)
+
+/*
+ * The crMaybeWaitUntil macros could have been more easily written in
+ * terms of the simple crReturn above, by writing things like
+ *
+ *       while (!condition) { crReturn(whatever); }
+ *
+ * (or do-while in the case of crWaitUntil). But it's better to do it
+ * directly by writing _once_ to crLine before first testing the
+ * condition, because this way it's robust against the condition check
+ * potentially freeing the entire coroutine state structure as a side
+ * effect (as long as it also evaluates false if it does that),
+ * because we don't write into crLine between the condition evaluating
+ * to false and the 'return' statement.
+ */
+#define crMaybeWaitUntil(c)                     \
+    do {                                        \
+        *crLine =__LINE__;                      \
+        case __LINE__: if (!(c)) return 0;      \
+    } while (0)
+#define crMaybeWaitUntilV(c)                    \
+    do {                                        \
+        *crLine =__LINE__;                      \
+        case __LINE__: if (!(c)) return;        \
+    } while (0)
+#define crWaitUntil(c)                          \
+    do {                                        \
+        *crLine =__LINE__; return;              \
+        case __LINE__: if (!(c)) return 0;      \
+    } while (0)
+#define crWaitUntilV(c)                         \
+    do {                                        \
+        *crLine =__LINE__; return;              \
+        case __LINE__: if (!(c)) return;        \
+    } while (0)
 
 #endif /* PUTTY_SSHCR_H */

+ 13 - 7
source/putty/sshshare.c

@@ -2052,6 +2052,12 @@ static const Plug_vtable ssh_sharing_listen_plugvt = {
     share_listen_accepting
 };
 
+void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
+                                     ConnectionLayer *cl)
+{
+    sharestate->cl = cl;
+}
+
 /*
  * Init function for connection sharing. We either open a listening
  * socket and become an upstream, or connect to an existing one and
@@ -2063,7 +2069,7 @@ static const Plug_vtable ssh_sharing_listen_plugvt = {
  * upstream) we return NULL.
  */
 Socket ssh_connection_sharing_init(
-    const char *host, int port, Conf *conf, ConnectionLayer *cl,
+    const char *host, int port, Conf *conf, Frontend *frontend,
     Plug sshplug, ssh_sharing_state **state)
 {
     int result, can_upstream, can_downstream;
@@ -2090,6 +2096,7 @@ Socket ssh_connection_sharing_init(
     sharestate = snew(struct ssh_sharing_state);
     sharestate->plugvt = &ssh_sharing_listen_plugvt;
     sharestate->listensock = NULL;
+    sharestate->cl = NULL;
 
     /*
      * Now hand off to a per-platform routine that either connects to
@@ -2115,16 +2122,16 @@ Socket ssh_connection_sharing_init(
             /* For this result, if 'logtext' is not NULL then it is an
              * error message indicating a reason why connection sharing
              * couldn't be set up _at all_ */
-            logeventf(cl->frontend,
+            logeventf(frontend,
                       "Could not set up connection sharing: %s", logtext);
         } else {
             /* Failing that, ds_err and us_err indicate why we
              * couldn't be a downstream and an upstream respectively */
             if (ds_err)
-                logeventf(cl->frontend, "Could not set up connection sharing"
+                logeventf(frontend, "Could not set up connection sharing"
                           " as downstream: %s", ds_err);
             if (us_err)
-                logeventf(cl->frontend, "Could not set up connection sharing"
+                logeventf(frontend, "Could not set up connection sharing"
                           " as upstream: %s", us_err);
         }
 
@@ -2142,7 +2149,7 @@ Socket ssh_connection_sharing_init(
          */
 
         /* 'logtext' is a local endpoint address */
-        logeventf(cl->frontend,
+        logeventf(frontend,
                   "Using existing shared connection at %s", logtext);
 
         *state = NULL;
@@ -2159,12 +2166,11 @@ Socket ssh_connection_sharing_init(
          */
 
         /* 'logtext' is a local endpoint address */
-        logeventf(cl->frontend, "Sharing this connection at %s", logtext);
+        logeventf(frontend, "Sharing this connection at %s", logtext);
 
         *state = sharestate;
         sharestate->listensock = sock;
         sharestate->connections = newtree234(share_connstate_cmp);
-        sharestate->cl = cl;
         sharestate->server_verstring = NULL;
         sharestate->sockname = sockname;
         sharestate->nextid = 1;

+ 23 - 6
source/putty/sshverstring.c

@@ -41,14 +41,17 @@ struct ssh_verstring_state {
 
 static void ssh_verstring_free(BinaryPacketProtocol *bpp);
 static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp);
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
 static PktOut *ssh_verstring_new_pktout(int type);
-static void ssh_verstring_format_packet(BinaryPacketProtocol *bpp, PktOut *);
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+                                          const char *msg, int category);
 
 static const struct BinaryPacketProtocolVtable ssh_verstring_vtable = {
     ssh_verstring_free,
     ssh_verstring_handle_input,
+    ssh_verstring_handle_output,
     ssh_verstring_new_pktout,
-    ssh_verstring_format_packet,
+    ssh_verstring_queue_disconnect,
 };
 
 static void ssh_detect_bugs(struct ssh_verstring_state *s);
@@ -97,6 +100,7 @@ BinaryPacketProtocol *ssh_verstring_new(
     s->send_early = !ssh_version_includes_v1(protoversion);
 
     s->bpp.vt = &ssh_verstring_vtable;
+    ssh_bpp_common_setup(&s->bpp);
     return &s->bpp;
 }
 
@@ -197,6 +201,12 @@ static void ssh_verstring_send(struct ssh_verstring_state *s)
     vs_logevent(("We claim version: %s", s->our_vstring));
 }
 
+#define BPP_WAITFOR(minlen) do                          \
+    {                                                   \
+        crMaybeWaitUntilV(                              \
+            bufchain_size(s->bpp.in_raw) >= (minlen));  \
+    } while (0)
+
 void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
 {
     struct ssh_verstring_state *s =
@@ -221,8 +231,7 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
          * Every time round this loop, we're at the start of a new
          * line, so look for the prefix.
          */
-        crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) >=
-                          s->prefix_wanted.len);
+        BPP_WAITFOR(s->prefix_wanted.len);
         bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len);
         if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) {
             bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len);
@@ -237,7 +246,9 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
             void *data;
             char *nl;
 
-            crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) > 0);
+            /* Wait to receive at least 1 byte, but then consume more
+             * than that if it's there. */
+            BPP_WAITFOR(1);
             bufchain_prefix(s->bpp.in_raw, &data, &len);
             if ((nl = memchr(data, '\012', len)) != NULL) {
                 bufchain_consume(s->bpp.in_raw, nl - (char *)data + 1);
@@ -387,7 +398,7 @@ static PktOut *ssh_verstring_new_pktout(int type)
     return NULL;
 }
 
-static void ssh_verstring_format_packet(BinaryPacketProtocol *bpp, PktOut *pkg)
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp)
 {
     assert(0 && "Should never try to send packets during SSH version "
            "string exchange");
@@ -600,3 +611,9 @@ int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp)
         FROMFIELD(bpp, struct ssh_verstring_state, bpp);
     return s->remote_bugs;
 }
+
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+                                           const char *msg, int category)
+{
+    /* No way to send disconnect messages at this stage of the protocol! */
+}