X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/ff3187f600cb2923cd42df948fc181c3bb26600b..39934deb5202149f98198c111a35c21cb4d0d0f8:/ssh.c diff --git a/ssh.c b/ssh.c index 57743cff..1d8e6b8d 100644 --- a/ssh.c +++ b/ssh.c @@ -329,6 +329,7 @@ static unsigned char *ssh2_mpint_fmt(Bignum b, int *len); static void ssh2_pkt_addmp(struct Packet *, Bignum b); static int ssh2_pkt_construct(Ssh, struct Packet *); static void ssh2_pkt_send(Ssh, struct Packet *); +static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, @@ -511,6 +512,7 @@ struct ssh_rportfwd { struct Packet { long length; int type; + unsigned long sequence; unsigned char *data; unsigned char *body; long savedpos; @@ -528,6 +530,8 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); +static void ssh1_protocol_setup(Ssh ssh); +static void ssh2_protocol_setup(Ssh ssh); static void ssh_size(void *handle, int width, int height); static void ssh_special(void *handle, Telnet_Special); static int ssh2_try_send(struct ssh_channel *c); @@ -557,6 +561,8 @@ struct rdpkt2_state_tag { struct Packet *pktin; }; +typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); + struct ssh_tag { const struct plug_function_table *fn; /* the above field _must_ be first in the structure */ @@ -615,6 +621,9 @@ struct ssh_tag { int size_needed, eof_needed; + struct Packet **queue; + int queuelen, queuesize; + int queueing; unsigned char *deferred_send_data; int deferred_len, deferred_size; @@ -650,8 +659,8 @@ struct ssh_tag { int ssh2_rdpkt_crstate; int do_ssh_init_crstate; int ssh_gotdata_crstate; - int ssh1_protocol_crstate; int do_ssh1_login_crstate; + int do_ssh1_connection_crstate; int do_ssh2_transport_crstate; int do_ssh2_authconn_crstate; @@ -663,6 +672,9 @@ struct ssh_tag { struct rdpkt1_state_tag rdpkt1_state; struct rdpkt2_state_tag rdpkt2_state; + /* ssh1 and ssh2 use this for different things, but both use it */ + int protocol_initial_phase_done; + void (*protocol) (Ssh ssh, unsigned char *in, int inlen, struct Packet *pkt); struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); @@ -680,6 +692,17 @@ struct ssh_tag { */ void *agent_response; int agent_response_len; + + /* + * Dispatch table for packet types that we may have to deal + * with at any time. + */ + handler_fn_t packet_dispatch[256]; + + /* + * This module deals with sending keepalives. + */ + Pinger pinger; }; #define logevent(s) logevent(ssh->frontend, s) @@ -869,8 +892,6 @@ static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen) crBegin(ssh->ssh1_rdpkt_crstate); - next_packet: - st->pktin = ssh_new_packet(); st->pktin->type = 0; @@ -988,48 +1009,6 @@ static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen) nblanks, &blank); } - if (st->pktin->type == SSH1_SMSG_STDOUT_DATA || - st->pktin->type == SSH1_SMSG_STDERR_DATA || - st->pktin->type == SSH1_MSG_DEBUG || - st->pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE || - st->pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - long stringlen = GET_32BIT(st->pktin->body); - if (stringlen + 4 != st->pktin->length) { - bombout(("Received data packet with bogus string length")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - } - - if (st->pktin->type == SSH1_MSG_DEBUG) { - char *buf, *msg; - int msglen; - - ssh_pkt_getstring(st->pktin, &msg, &msglen); - buf = dupprintf("Remote debug message: %.*s", msglen, msg); - logevent(buf); - sfree(buf); - - ssh_free_packet(st->pktin); - goto next_packet; - } else if (st->pktin->type == SSH1_MSG_IGNORE) { - /* do nothing */ - ssh_free_packet(st->pktin); - goto next_packet; - } - - if (st->pktin->type == SSH1_MSG_DISCONNECT) { - /* log reason code in disconnect message */ - char *msg; - int msglen; - - ssh_pkt_getstring(st->pktin, &msg, &msglen); - - bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg)); - ssh_free_packet(st->pktin); - crStop(NULL); - } - crFinish(st->pktin); } @@ -1039,8 +1018,6 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) crBegin(ssh->ssh2_rdpkt_crstate); - next_packet: - st->pktin = ssh_new_packet(); st->pktin->type = 0; @@ -1132,7 +1109,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) ssh_free_packet(st->pktin); crStop(NULL); } - st->incoming_sequence++; /* whether or not we MACed */ + + st->pktin->sequence = st->incoming_sequence++; /* * Decompress packet payload. @@ -1187,113 +1165,6 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) nblanks, &blank); } - switch (st->pktin->type) { - /* - * These packets we must handle instantly. - */ - case SSH2_MSG_DISCONNECT: - { - /* log reason code in disconnect message */ - char *buf, *msg; - int nowlen, reason, msglen; - - reason = ssh_pkt_getuint32(st->pktin); - ssh_pkt_getstring(st->pktin, &msg, &msglen); - - if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { - buf = dupprintf("Received disconnect message (%s)", - ssh2_disconnect_reasons[reason]); - } else { - buf = dupprintf("Received disconnect message (unknown" - " type %d)", reason); - } - logevent(buf); - sfree(buf); - buf = dupprintf("Disconnection message text: %n%.*s", - &nowlen, msglen, msg); - logevent(buf); - bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"", - reason, - (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? - ssh2_disconnect_reasons[reason] : "unknown", - buf+nowlen)); - sfree(buf); - ssh_free_packet(st->pktin); - crStop(NULL); - } - break; - case SSH2_MSG_IGNORE: - ssh_free_packet(st->pktin); - goto next_packet; - case SSH2_MSG_DEBUG: - { - /* log the debug message */ - char *buf, *msg; - int msglen; - int always_display; - - /* XXX maybe we should actually take notice of this */ - always_display = ssh2_pkt_getbool(st->pktin); - ssh_pkt_getstring(st->pktin, &msg, &msglen); - - buf = dupprintf("Remote debug message: %.*s", msglen, msg); - logevent(buf); - sfree(buf); - } - ssh_free_packet(st->pktin); - goto next_packet; - - /* - * These packets we need do nothing about here. - */ - case SSH2_MSG_UNIMPLEMENTED: - case SSH2_MSG_SERVICE_REQUEST: - case SSH2_MSG_SERVICE_ACCEPT: - case SSH2_MSG_KEXINIT: - case SSH2_MSG_NEWKEYS: - case SSH2_MSG_KEXDH_INIT: - case SSH2_MSG_KEXDH_REPLY: - /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */ - /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */ - case SSH2_MSG_KEX_DH_GEX_INIT: - case SSH2_MSG_KEX_DH_GEX_REPLY: - case SSH2_MSG_USERAUTH_REQUEST: - case SSH2_MSG_USERAUTH_FAILURE: - case SSH2_MSG_USERAUTH_SUCCESS: - case SSH2_MSG_USERAUTH_BANNER: - case SSH2_MSG_USERAUTH_PK_OK: - /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */ - /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */ - case SSH2_MSG_USERAUTH_INFO_RESPONSE: - case SSH2_MSG_GLOBAL_REQUEST: - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - case SSH2_MSG_CHANNEL_OPEN: - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - break; - - /* - * For anything else we send SSH2_MSG_UNIMPLEMENTED. - */ - default: - { - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); - ssh2_pkt_adduint32(pktout, st->incoming_sequence - 1); - ssh2_pkt_send(ssh, pktout); - } - break; - } - crFinish(st->pktin); } @@ -1726,9 +1597,38 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) } /* - * Construct and send an SSH2 packet immediately. + * Routines called from the main SSH code to send packets. There + * are quite a few of these, because we have two separate + * mechanisms for delaying the sending of packets: + * + * - In order to send an IGNORE message and a password message in + * a single fixed-length blob, we require the ability to + * concatenate the encrypted forms of those two packets _into_ a + * single blob and then pass it to our transport + * layer in one go. Hence, there's a deferment mechanism which + * works after packet encryption. + * + * - In order to avoid sending any connection-layer messages + * during repeat key exchange, we have to queue up any such + * outgoing messages _before_ they are encrypted (and in + * particular before they're allocated sequence numbers), and + * then send them once we've finished. + * + * I call these mechanisms `defer' and `queue' respectively, so as + * to distinguish them reasonably easily. + * + * The functions send_noqueue() and defer_noqueue() free the packet + * structure they are passed. Every outgoing packet goes through + * precisely one of these functions in its life; packets passed to + * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of + * these or get queued, and then when the queue is later emptied + * the packets are all passed to defer_noqueue(). + */ + +/* + * Send an SSH2 packet immediately, without queuing or deferring. */ -static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) +static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) { int len; int backlog; @@ -1740,17 +1640,9 @@ static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) } /* - * Construct an SSH2 packet and add it to a deferred data block. - * Useful for sending multiple packets in a single sk_write() call, - * to prevent a traffic-analysing listener from being able to work - * out the length of any particular packet (such as the password - * packet). - * - * Note that because SSH2 sequence-numbers its packets, this can - * NOT be used as an m4-style `defer' allowing packets to be - * constructed in one order and sent in another. + * Defer an SSH2 packet. */ -static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) +static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt) { int len = ssh2_pkt_construct(ssh, pkt); if (ssh->deferred_len + len > ssh->deferred_size) { @@ -1765,8 +1657,56 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) } /* + * Queue an SSH2 packet. + */ +static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt) +{ + assert(ssh->queueing); + + if (ssh->queuelen >= ssh->queuesize) { + ssh->queuesize = ssh->queuelen + 32; + ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *); + } + + ssh->queue[ssh->queuelen++] = pkt; +} + +/* + * Either queue or send a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_send_noqueue(ssh, pkt); +} + +/* + * Either queue or defer a packet, depending on whether queueing is + * set. + */ +static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) +{ + if (ssh->queueing) + ssh2_pkt_queue(ssh, pkt); + else + ssh2_pkt_defer_noqueue(ssh, pkt); +} + +/* * Send the whole deferred data block constructed by * ssh2_pkt_defer() or SSH1's defer_packet(). + * + * The expected use of the defer mechanism is that you call + * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If + * not currently queueing, this simply sets up deferred_send_data + * and then sends it. If we _are_ currently queueing, the calls to + * ssh2_pkt_defer() put the deferred packets on to the queue + * instead, and therefore ssh_pkt_defersend() has no deferred data + * to send. Hence, there's no need to make it conditional on + * ssh->queueing. */ static void ssh_pkt_defersend(Ssh ssh) { @@ -1780,6 +1720,24 @@ static void ssh_pkt_defersend(Ssh ssh) ssh_throttle_all(ssh, 1, backlog); } +/* + * Send all queued SSH2 packets. We send them by means of + * ssh2_pkt_defer_noqueue(), in case they included a pair of + * packets that needed to be lumped together. + */ +static void ssh2_pkt_queuesend(Ssh ssh) +{ + int i; + + assert(!ssh->queueing); + + for (i = 0; i < ssh->queuelen; i++) + ssh2_pkt_defer_noqueue(ssh, ssh->queue[i]); + ssh->queuelen = 0; + + ssh_pkt_defersend(ssh); +} + #if 0 void bndebug(char *string, Bignum b) { @@ -2180,6 +2138,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c) logevent("Using SSH protocol version 2"); sk_write(ssh->s, verstring, strlen(verstring)); ssh->protocol = ssh2_protocol; + ssh2_protocol_setup(ssh); ssh->version = 2; ssh->s_rdpkt = ssh2_rdpkt; } else { @@ -2197,11 +2156,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c) logevent("Using SSH protocol version 1"); sk_write(ssh->s, verstring, strlen(verstring)); ssh->protocol = ssh1_protocol; + ssh1_protocol_setup(ssh); ssh->version = 1; ssh->s_rdpkt = ssh1_rdpkt; } update_specials_menu(ssh->frontend); ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh); sfree(s->vstring); @@ -2261,6 +2222,7 @@ static void ssh_do_close(Ssh ssh) if (ssh->s) { sk_close(ssh->s); ssh->s = NULL; + notify_remote_exit(ssh->frontend); } /* * Now we must shut down any port and X forwardings going @@ -2372,6 +2334,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port, 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; + notify_remote_exit(ssh->frontend); return err; } @@ -2579,6 +2542,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, crBegin(ssh->do_ssh1_login_crstate); + random_init(); + if (!pktin) crWaitUntil(pktin); @@ -3481,18 +3446,10 @@ void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) } } -static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, - struct Packet *pktin) +static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) { - crBegin(ssh->ssh1_protocol_crstate); - - random_init(); - - while (!do_ssh1_login(ssh, in, inlen, pktin)) { - crReturnV; - } - if (ssh->state == SSH_STATE_CLOSED) - crReturnV; + crBegin(ssh->do_ssh1_connection_crstate); if (ssh->cfg.agentfwd && agent_exists()) { logevent("Requesting agent forwarding"); @@ -4108,6 +4065,74 @@ static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, } /* + * Handle the top-level SSH2 protocol. + */ +static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) +{ + char *buf, *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + buf = dupprintf("Remote debug message: %.*s", msglen, msg); + logevent(buf); + sfree(buf); +} + +static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *msg; + int msglen; + + ssh_pkt_getstring(pktin, &msg, &msglen); + bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg)); +} + +void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) +{ + /* Do nothing, because we're ignoring it! Duhh. */ +} + +static void ssh1_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages are handled by the coroutines. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = NULL; + + /* + * These special message types we install handlers for. + */ + ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect; + ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore; + ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; +} + +static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, + struct Packet *pktin) +{ + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin && ssh->packet_dispatch[pktin->type]) { + ssh->packet_dispatch[pktin->type](ssh, pktin); + return; + } + + if (!ssh->protocol_initial_phase_done) { + if (do_ssh1_login(ssh, in, inlen, pktin)) + ssh->protocol_initial_phase_done = TRUE; + else + return; + } + + do_ssh1_connection(ssh, in, inlen, pktin); +} + +/* * Utility routine for decoding comma-separated strings in KEXINIT. */ static int in_commasep_string(char *needle, char *haystack, int haylen) @@ -4256,6 +4281,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, int i, j, cipherstr_started; /* + * Enable queueing of outgoing auth- or connection-layer + * packets while we are in the middle of a key exchange. + */ + ssh->queueing = TRUE; + + /* * Construct and send our key exchange packet. */ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT); @@ -4353,7 +4384,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, ssh->exhash = ssh->exhashbase; sha_string(&ssh->exhash, s->pktout->data + 5, s->pktout->length - 5); - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); if (!pktin) crWaitUntil(pktin); @@ -4515,7 +4546,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, s->pbits = 512 << ((s->nbits - 1) / 64); s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); ssh2_pkt_adduint32(s->pktout, s->pbits); - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); crWaitUntil(pktin); if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { @@ -4545,7 +4576,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); s->pktout = ssh2_pkt_init(s->kex_init_value); ssh2_pkt_addmp(s->pktout, s->e); - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); crWaitUntil(pktin); if (pktin->type != s->kex_reply_value) { @@ -4610,7 +4641,14 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * Send SSH2_MSG_NEWKEYS. */ s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS); - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + /* + * Now our end of the key exchange is complete, we can send all + * our queued higher-layer packets. + */ + ssh->queueing = FALSE; + ssh2_pkt_queuesend(ssh); /* * Expect SSH2_MSG_NEWKEYS from server. @@ -4706,7 +4744,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * it would only confuse the layer above. */ if (!s->first_kex) { - crReturn(0); + crReturn(1); } s->first_kex = 0; @@ -4794,6 +4832,15 @@ static void ssh2_set_window(struct ssh_channel *c, unsigned newwin) } } +void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) +{ + unsigned i = ssh_pkt_getuint32(pktin); + struct ssh_channel *c; + c = find234(ssh->channels, &i, ssh_channelfind); + if (c && !c->closes) + c->v.v2.remwindow += ssh_pkt_getuint32(pktin); +} + /* * Handle the SSH2 userauth and connection layers. */ @@ -5452,7 +5499,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_addstring(s->pktout, "No more passwords available" " to try"); ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); logevent("Unable to authenticate"); connection_fatal(ssh->frontend, "Unable to authenticate"); @@ -5653,7 +5700,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_addstring(s->pktout, "No supported authentication" " methods available"); ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); ssh_closing((Plug)ssh, NULL, 0, 0); crStopV; } @@ -5669,6 +5716,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh->channels = newtree234(ssh_channelcmp); /* + * Set up handlers for some connection protocol messages, so we + * don't have to handle them repeatedly in this coroutine. + */ + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = + ssh2_msg_channel_window_adjust; + + /* * Create the main session channel. */ if (!ssh->cfg.ssh_no_shell) { @@ -5723,17 +5777,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_adduint32(s->pktout, x11_get_screen_number(ssh->cfg.x11_display)); ssh2_pkt_send(ssh, s->pktout); - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { @@ -5889,17 +5933,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_adduint32(s->pktout, sport); ssh2_pkt_send(ssh, s->pktout); - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue;/* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_REQUEST_SUCCESS) { if (pktin->type != SSH2_MSG_REQUEST_FAILURE) { @@ -5930,17 +5964,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_addbool(s->pktout, 1); /* want reply */ ssh2_pkt_send(ssh, s->pktout); - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { @@ -5982,17 +6006,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_send(ssh, s->pktout); ssh->state = SSH_STATE_INTERMED; - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { @@ -6049,17 +6063,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->env_left = s->num_env; while (s->env_left > 0) { - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { @@ -6118,17 +6122,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_addbool(s->pktout, 1); /* want reply */ } ssh2_pkt_send(ssh, s->pktout); - do { - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - } - } while (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { bombout(("Unexpected response to shell/command request:" @@ -6332,20 +6328,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION); ssh2_pkt_addstring(s->pktout, "All open channels closed"); ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); #endif ssh_closing((Plug)ssh, NULL, 0, 0); crStopV; } continue; /* remote sends close; ignore (FIXME) */ - } else if (pktin->type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - unsigned i = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - c = find234(ssh->channels, &i, ssh_channelfind); - if (!c || c->closes) - continue; /* nonexistent or closing channel */ - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - s->try_send = TRUE; } else if (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { unsigned i = ssh_pkt_getuint32(pktin); struct ssh_channel *c; @@ -6430,7 +6418,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION); ssh2_pkt_addstring(s->pktout, buf); ssh2_pkt_addstring(s->pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_noqueue(ssh, s->pktout); connection_fatal(ssh->frontend, "%s", buf); ssh_closing((Plug)ssh, NULL, 0, 0); crStopV; @@ -6716,14 +6704,148 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } /* + * Handlers for SSH2 messages that might arrive at any moment. + */ +void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) +{ + /* log reason code in disconnect message */ + char *buf, *msg; + int nowlen, reason, msglen; + + reason = ssh_pkt_getuint32(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { + buf = dupprintf("Received disconnect message (%s)", + ssh2_disconnect_reasons[reason]); + } else { + buf = dupprintf("Received disconnect message (unknown" + " type %d)", reason); + } + logevent(buf); + sfree(buf); + buf = dupprintf("Disconnection message text: %n%.*s", + &nowlen, msglen, msg); + logevent(buf); + bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"", + reason, + (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown", + buf+nowlen)); + sfree(buf); +} + +void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) +{ + /* log the debug message */ + char *buf, *msg; + int msglen; + int always_display; + + /* XXX maybe we should actually take notice of this */ + always_display = ssh2_pkt_getbool(pktin); + ssh_pkt_getstring(pktin, &msg, &msglen); + + buf = dupprintf("Remote debug message: %.*s", msglen, msg); + logevent(buf); + sfree(buf); +} + +void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) +{ + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); + ssh2_pkt_adduint32(pktout, pktin->sequence); + /* + * UNIMPLEMENTED messages MUST appear in the same order as the + * messages they respond to. Hence, never queue them. + */ + ssh2_pkt_send_noqueue(ssh, pktout); +} + +/* * Handle the top-level SSH2 protocol. */ +static void ssh2_protocol_setup(Ssh ssh) +{ + int i; + + /* + * Most messages cause SSH2_MSG_UNIMPLEMENTED. + */ + for (i = 0; i < 256; i++) + ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + + /* + * Any message we actually understand, we set to NULL so that + * the coroutines will get it. + */ + ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL; + ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL; + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL; + ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; + ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL; + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */ + /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */ + ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL; + ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL; + ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL; + + /* + * These special message types we install handlers for. + */ + ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; + ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with ssh1 */ + ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; +} + static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin) { - if (do_ssh2_transport(ssh, in, inlen, pktin) == 0) + if (ssh->state == SSH_STATE_CLOSED) + return; + + if (pktin && ssh->packet_dispatch[pktin->type]) { + ssh->packet_dispatch[pktin->type](ssh, pktin); return; - do_ssh2_authconn(ssh, in, inlen, pktin); + } + + if (!ssh->protocol_initial_phase_done || + (pktin && pktin->type >= 20 && pktin->type < 50)) { + if (do_ssh2_transport(ssh, in, inlen, pktin) && + !ssh->protocol_initial_phase_done) { + ssh->protocol_initial_phase_done = TRUE; + /* + * Allow authconn to initialise itself. + */ + do_ssh2_authconn(ssh, NULL, 0, NULL); + } + } else { + do_ssh2_authconn(ssh, in, inlen, pktin); + } } /* @@ -6779,7 +6901,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->ssh2_rdpkt_crstate = 0; ssh->do_ssh_init_crstate = 0; ssh->ssh_gotdata_crstate = 0; - ssh->ssh1_protocol_crstate = 0; + ssh->do_ssh1_connection_crstate = 0; ssh->do_ssh1_login_crstate = 0; ssh->do_ssh2_transport_crstate = 0; ssh->do_ssh2_authconn_crstate = 0; @@ -6790,6 +6912,9 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->mainchan = NULL; ssh->throttled_all = 0; ssh->v1_stdout_throttling = 0; + ssh->queue = NULL; + ssh->queuelen = ssh->queuesize = 0; + ssh->queueing = FALSE; *backend_handle = ssh; @@ -6814,6 +6939,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->protocol = NULL; + ssh->protocol_initial_phase_done = FALSE; + + ssh->pinger = NULL; + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) return p; @@ -6853,6 +6982,10 @@ static void ssh_free(void *handle) dh_cleanup(ssh->kex_ctx); sfree(ssh->savedhost); + while (ssh->queuelen-- > 0) + ssh_free_packet(ssh->queue[ssh->queuelen]); + sfree(ssh->queue); + if (ssh->channels) { while ((c = delpos234(ssh->channels, 0)) != NULL) { switch (c->type) { @@ -6889,6 +7022,8 @@ static void ssh_free(void *handle) if (ssh->s) ssh_do_close(ssh); sfree(ssh); + if (ssh->pinger) + pinger_free(ssh->pinger); } /* @@ -6903,6 +7038,7 @@ static void ssh_free(void *handle) static void ssh_reconfig(void *handle, Config *cfg) { Ssh ssh = (Ssh) handle; + pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); ssh->cfg = *cfg; /* STRUCTURE COPY */ } @@ -7096,7 +7232,7 @@ static void ssh_special(void *handle, Telnet_Special code) } else { pktout = ssh2_pkt_init(SSH2_MSG_IGNORE); ssh2_pkt_addstring_start(pktout); - ssh2_pkt_send(ssh, pktout); + ssh2_pkt_send_noqueue(ssh, pktout); } } else if (code == TS_BRK) { if (ssh->state == SSH_STATE_CLOSED