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,
struct Packet {
long length;
int type;
+ unsigned long sequence;
unsigned char *data;
unsigned char *body;
long savedpos;
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);
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 */
int size_needed, eof_needed;
+ struct Packet **queue;
+ int queuelen, queuesize;
+ int queueing;
unsigned char *deferred_send_data;
int deferred_len, deferred_size;
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;
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);
*/
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];
};
#define logevent(s) logevent(ssh->frontend, s)
crBegin(ssh->ssh1_rdpkt_crstate);
- next_packet:
-
st->pktin = ssh_new_packet();
st->pktin->type = 0;
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);
}
crBegin(ssh->ssh2_rdpkt_crstate);
- next_packet:
-
st->pktin = ssh_new_packet();
st->pktin->type = 0;
ssh_free_packet(st->pktin);
crStop(NULL);
}
- st->incoming_sequence++; /* whether or not we MACed */
+
+ st->pktin->sequence = st->incoming_sequence++;
/*
* Decompress packet payload.
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);
}
}
/*
- * 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 <network.h> 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().
*/
-static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt)
+
+/*
+ * Send an SSH2 packet immediately, without queuing or deferring.
+ */
+static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
{
int len;
int backlog;
}
/*
- * 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) {
}
/*
+ * 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)
{
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)
{
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 {
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;
}
crBegin(ssh->do_ssh1_login_crstate);
+ random_init();
+
if (!pktin)
crWaitUntil(pktin);
}
}
-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");
}
/*
+ * 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)
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);
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);
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) {
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) {
* 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.
* it would only confuse the layer above.
*/
if (!s->first_kex) {
- crReturn(0);
+ crReturn(1);
}
s->first_kex = 0;
}
}
+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.
*/
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");
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;
}
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) {
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) {
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) {
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) {
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) {
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) {
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:"
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;
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;
}
/*
+ * 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);
+ }
}
/*
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;
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;
ssh->protocol = NULL;
+ ssh->protocol_initial_phase_done = FALSE;
+
p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
if (p != NULL)
return p;
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) {
} 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