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,
int size_needed, eof_needed;
+ struct Packet **queue;
+ int queuelen, queuesize;
+ int queueing;
unsigned char *deferred_send_data;
int deferred_len, deferred_size;
struct Packet *pktout;
pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
ssh2_pkt_adduint32(pktout, st->incoming_sequence - 1);
- ssh2_pkt_send(ssh, pktout);
+ /* UNIMPLEMENTED messages MUST appear in the same order as
+ * the messages they respond to. Hence, never queue them. */
+ ssh2_pkt_send_noqueue(ssh, pktout);
}
break;
}
}
/*
- * 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) {
ssh_free_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)
{
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)
{
{
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.
*/
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.
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;
}
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;
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;
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;
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