1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Put all incoming SSH wire data into a bufchain.

I've completely removed the top-level coroutine ssh_gotdata(), and
replaced it with a system in which ssh_receive (which is a plug
function, i.e. called directly from the network code) simply adds the
incoming data to a new bufchain called ssh->incoming_data, and then
queues an idempotent callback to ensure that whatever function is
currently responsible for the top-level handling of wire data will be
invoked in the near future.

So the decisions that ssh_gotdata was previously making are now made
by changing which function is invoked by that idempotent callback:
when we finish doing SSH greeting exchange and move on to the packet-
structured main phase of the protocol, we just change
ssh->current_incoming_data_fn and ensure that the new function gets
called to take over anything still outstanding in the queue.

This simplifies the _other_ end of the API of the rdpkt functions. In
the previous commit, they stopped returning their 'struct Packet'
directly, and instead put it on a queue; in this commit, they're no
longer receiving a (data, length) pair in their parameter list, and
instead, they're just reading from ssh->incoming_data. So now, API-
wise, they take no arguments at all except the main 'ssh' state
structure.

It's not just the rdpkt functions that needed to change, of course.
The SSH greeting handlers have also had to switch to reading from
ssh->incoming_data, and are quite substantially rewritten as a result.
(I think they look simpler in the new style, personally.)

This new bufchain takes over from the previous queued_incoming_data,
which was only used at all in cases where we throttled the entire SSH
connection. Now, data is unconditionally left on the new bufchain
whether we're throttled or not, and the only question is whether we're
currently bothering to read it; so all the special-purpose code to
read data from a bufchain and pass it to rdpkt can go away, because
rdpkt itself already knows how to do that job.

One slightly fiddly point is that we now have to defer processing of
EOF from the SSH server: if we have data already in the incoming
bufchain and then the server slams the connection shut, we want to
process the data we've got _before_ reacting to the remote EOF, just
in case that data gives us some reason to change our mind about how we
react to the EOF, or a last-minute important piece of data we might
need to log.
This commit is contained in:
Simon Tatham 2018-05-18 07:22:57 +01:00
parent 2b57b84fa5
commit fe6caf563c

549
ssh.c
View File

@ -823,26 +823,21 @@ static void pq_clear(struct PacketQueue *pq)
}
struct rdpkt1_state_tag {
long len, pad, biglen, to_read;
long len, pad, biglen;
unsigned long realcrc, gotcrc;
unsigned char *p;
int i;
int chunk;
struct Packet *pktin;
};
struct rdpkt2_state_tag {
long len, pad, payload, packetlen, maclen;
int i;
int cipherblk;
unsigned long incoming_sequence;
struct Packet *pktin;
};
struct rdpkt2_bare_state_tag {
char length[4];
long packetlen;
int i;
unsigned long incoming_sequence;
struct Packet *pktin;
};
@ -960,7 +955,6 @@ struct ssh_tag {
int ssh1_rdpkt_crstate;
int ssh2_rdpkt_crstate;
int ssh2_bare_rdpkt_crstate;
int ssh_gotdata_crstate;
int do_ssh1_connection_crstate;
void *do_ssh_init_state;
@ -969,6 +963,11 @@ struct ssh_tag {
void *do_ssh2_authconn_state;
void *do_ssh_connection_init_state;
bufchain incoming_data;
struct IdempotentCallback incoming_data_consumer;
int incoming_data_seen_eof;
char *incoming_data_eof_message;
struct PacketQueue pq_full;
struct IdempotentCallback pq_full_consumer;
@ -981,8 +980,7 @@ struct ssh_tag {
void (*protocol) (Ssh ssh, const void *vin, int inlen,
struct Packet *pkt);
void (*s_rdpkt) (Ssh ssh, const unsigned char **data, int *datalen);
int (*do_ssh_init)(Ssh ssh, unsigned char c);
void (*current_incoming_data_fn) (Ssh ssh);
/*
* We maintain our own copy of a Conf structure here. That way,
@ -1015,15 +1013,9 @@ struct ssh_tag {
/*
* The SSH connection can be set as `frozen', meaning we are
* not currently accepting incoming data from the network. This
* is slightly more serious than setting the _socket_ as
* frozen, because we may already have had data passed to us
* from the network which we need to delay processing until
* after the freeze is lifted, so we also need a bufchain to
* store that data.
* not currently accepting incoming data from the network.
*/
int frozen;
bufchain queued_incoming_data;
/*
* Dispatch table for packet types that we may have to deal
@ -1476,7 +1468,7 @@ static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
* Update the *data and *datalen variables.
* Return a Packet structure when a packet is completed.
*/
static void ssh1_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
static void ssh1_rdpkt(Ssh ssh)
{
struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
@ -1488,11 +1480,11 @@ static void ssh1_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
st->pktin->type = 0;
st->pktin->length = 0;
for (st->i = st->len = 0; st->i < 4; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->len = (st->len << 8) + **data;
(*data)++, (*datalen)--;
{
unsigned char lenbuf[4];
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data, lenbuf, 4));
st->len = toint(GET_32BIT_MSB_FIRST(lenbuf));
}
st->pad = 8 - (st->len % 8);
@ -1509,20 +1501,9 @@ static void ssh1_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
st->pktin->maxlen = st->biglen;
st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char);
st->to_read = st->biglen;
st->p = st->pktin->data;
while (st->to_read > 0) {
st->chunk = st->to_read;
while ((*datalen) == 0)
crReturnV;
if (st->chunk > (*datalen))
st->chunk = (*datalen);
memcpy(st->p, *data, st->chunk);
*data += st->chunk;
*datalen -= st->chunk;
st->p += st->chunk;
st->to_read -= st->chunk;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data, st->biglen));
if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data,
st->biglen, NULL)) {
@ -1736,7 +1717,7 @@ static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
pkt->length += (pkt->body - pkt->data);
}
static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
static void ssh2_rdpkt(Ssh ssh)
{
struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
@ -1780,12 +1761,9 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
unsigned char);
/* Read an amount corresponding to the MAC. */
for (st->i = 0; st->i < st->maclen; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data, st->maclen));
st->packetlen = 0;
{
@ -1797,12 +1775,11 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
for (;;) { /* Once around this loop per cipher block. */
/* Read another cipher-block's worth, and tack it onto the end. */
for (st->i = 0; st->i < st->cipherblk; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data + (st->packetlen +
st->maclen),
st->cipherblk));
/* Decrypt one more block (a little further back in the stream). */
ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
st->pktin->data + st->packetlen,
@ -1834,12 +1811,9 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
* OpenSSH encrypt-then-MAC mode: the packet length is
* unencrypted, unless the cipher supports length encryption.
*/
for (st->i = st->len = 0; st->i < 4; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data, st->pktin->data, 4));
/* Cipher supports length decryption, so do it */
if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
/* Keep the packet the same though, so the MAC passes */
@ -1878,12 +1852,9 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
/*
* Read the remainder of the packet.
*/
for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data, st->pktin->data + 4,
st->packetlen + st->maclen - 4));
/*
* Check the MAC.
@ -1908,12 +1879,9 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
* Acquire and decrypt the first block of the packet. This will
* contain the length and padding details.
*/
for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data, st->cipherblk));
if (ssh->sccipher)
ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
@ -1951,13 +1919,11 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
/*
* Read and decrypt the remainder of the packet.
*/
for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data + st->cipherblk,
st->packetlen + st->maclen - st->cipherblk));
/* Decrypt everything _except_ the MAC. */
if (ssh->sccipher)
ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
@ -2054,25 +2020,21 @@ static void ssh2_rdpkt(Ssh ssh, const unsigned char **data, int *datalen)
crFinishV;
}
static void ssh2_bare_connection_rdpkt(
Ssh ssh, const unsigned char **data, int *datalen)
static void ssh2_bare_connection_rdpkt(Ssh ssh)
{
struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
crBegin(ssh->ssh2_bare_rdpkt_crstate);
while (1) {
/*
* Read the packet length field.
*/
for (st->i = 0; st->i < 4; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->length[st->i] = *(*data)++;
(*datalen)--;
/* Read the length field. */
{
unsigned char lenbuf[4];
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data, lenbuf, 4));
st->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
}
st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length));
if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) {
bombout(("Invalid packet length received"));
crStopV;
@ -2088,12 +2050,9 @@ static void ssh2_bare_connection_rdpkt(
/*
* Read the remainder of the packet.
*/
for (st->i = 0; st->i < st->packetlen; st->i++) {
while ((*datalen) == 0)
crReturnV;
st->pktin->data[st->i] = *(*data)++;
(*datalen)--;
}
crMaybeWaitUntilV(bufchain_try_fetch_consume(
&ssh->incoming_data,
st->pktin->data, st->packetlen));
/*
* pktin->body and pktin->length should identify the semantic
@ -3237,15 +3196,15 @@ static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
sfree(verstring);
}
static int do_ssh_init(Ssh ssh, unsigned char c)
static void do_ssh_init(Ssh ssh)
{
static const char protoname[] = "SSH-";
struct do_ssh_init_state {
int crLine;
int vslen;
char version[10];
char *vstring;
char *version;
int vstrsize;
int i;
int proto1, proto2;
@ -3254,55 +3213,91 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
crBeginState;
/* Search for a line beginning with the protocol name prefix in
* the input. */
for (;;) {
for (s->i = 0; protoname[s->i]; s->i++) {
if ((char)c != protoname[s->i]) goto no;
crReturn(1);
/*
* Search for a line beginning with the protocol name prefix in
* the input.
*/
s->i = 0;
while (1) {
char prefix[sizeof(protoname)-1];
/*
* Every time round this loop, we're at the start of a new
* line, so look for the prefix.
*/
crMaybeWaitUntilV(
bufchain_size(&ssh->incoming_data) >= sizeof(prefix));
bufchain_fetch(&ssh->incoming_data, prefix, sizeof(prefix));
if (!memcmp(prefix, protoname, sizeof(prefix))) {
bufchain_consume(&ssh->incoming_data, sizeof(prefix));
break;
}
/*
* If we didn't find it, consume data until we see a newline.
*/
while (1) {
int len;
void *data;
char *nl;
crMaybeWaitUntilV(bufchain_size(&ssh->incoming_data) > 0);
bufchain_prefix(&ssh->incoming_data, &data, &len);
if ((nl = memchr(data, '\012', len)) != NULL) {
bufchain_consume(&ssh->incoming_data, nl - (char *)data + 1);
break;
} else {
bufchain_consume(&ssh->incoming_data, len);
}
}
break;
no:
while (c != '\012')
crReturn(1);
crReturn(1);
}
ssh->session_started = TRUE;
ssh->agentfwd_enabled = FALSE;
ssh->rdpkt2_state.incoming_sequence = 0;
/*
* Now read the rest of the greeting line.
*/
s->vstrsize = sizeof(protoname) + 16;
s->vstring = snewn(s->vstrsize, char);
strcpy(s->vstring, protoname);
s->vslen = strlen(protoname);
s->i = 0;
while (1) {
if (s->vslen >= s->vstrsize - 1) {
s->vstrsize += 16;
do {
int len;
void *data;
char *nl;
crMaybeWaitUntilV(bufchain_size(&ssh->incoming_data) > 0);
bufchain_prefix(&ssh->incoming_data, &data, &len);
if ((nl = memchr(data, '\012', len)) != NULL) {
len = nl - (char *)data + 1;
}
if (s->vslen + len >= s->vstrsize - 1) {
s->vstrsize = (s->vslen + len) * 5 / 4 + 32;
s->vstring = sresize(s->vstring, s->vstrsize, char);
}
s->vstring[s->vslen++] = c;
if (s->i >= 0) {
if (c == '-') {
s->version[s->i] = '\0';
s->i = -1;
} else if (s->i < sizeof(s->version) - 1)
s->version[s->i++] = c;
} else if (c == '\012')
break;
crReturn(1); /* get another char */
}
ssh->agentfwd_enabled = FALSE;
ssh->rdpkt2_state.incoming_sequence = 0;
memcpy(s->vstring + s->vslen, data, len);
s->vslen += len;
bufchain_consume(&ssh->incoming_data, len);
} while (s->vstring[s->vslen-1] != '\012');
s->vstring[s->vslen] = 0;
s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
logeventf(ssh, "Server version: %s", s->vstring);
ssh_detect_bugs(ssh, s->vstring);
/*
* Decide which SSH protocol version to support.
*/
s->version = dupprintf(
"%.*s", (int)strcspn(s->vstring + strlen(protoname), "-"),
s->vstring + strlen(protoname));
/* Anything strictly below "2.0" means protocol 1 is supported. */
s->proto1 = ssh_versioncmp(s->version, "2.0") < 0;
@ -3313,13 +3308,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
if (!s->proto1) {
bombout(("SSH protocol version 1 required by our configuration "
"but not provided by server"));
crStop(0);
crStopV;
}
} else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) {
if (!s->proto2) {
bombout(("SSH protocol version 2 required by our configuration "
"but server only provides (old, insecure) SSH-1"));
crStop(0);
crStopV;
}
} else {
/* No longer support values 1 or 2 for CONF_sshprot */
@ -3337,6 +3332,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
if (conf_get_int(ssh->conf, CONF_sshprot) != 3)
ssh_send_verstring(ssh, protoname, s->version);
sfree(s->version);
if (ssh->version == 2) {
size_t len;
/*
@ -3352,15 +3349,16 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
*/
ssh->protocol = ssh2_protocol;
ssh2_protocol_setup(ssh);
ssh->s_rdpkt = ssh2_rdpkt;
ssh->current_incoming_data_fn = ssh2_rdpkt;
} else {
/*
* Initialise SSH-1 protocol.
*/
ssh->protocol = ssh1_protocol;
ssh1_protocol_setup(ssh);
ssh->s_rdpkt = ssh1_rdpkt;
ssh->current_incoming_data_fn = ssh1_rdpkt;
}
queue_idempotent_callback(&ssh->incoming_data_consumer);
if (ssh->version == 2)
do_ssh2_transport(ssh, NULL, -1, NULL);
@ -3370,10 +3368,10 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
sfree(s->vstring);
crFinish(0);
crFinishV;
}
static int do_ssh_connection_init(Ssh ssh, unsigned char c)
static void do_ssh_connection_init(Ssh ssh)
{
/*
* Ordinary SSH begins with the banner "SSH-x.y-...". This is just
@ -3390,8 +3388,8 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
struct do_ssh_connection_init_state {
int crLine;
int vslen;
char version[10];
char *vstring;
char *version;
int vstrsize;
int i;
};
@ -3399,47 +3397,81 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
crBeginState;
/* Search for a line beginning with the protocol name prefix in
* the input. */
for (;;) {
for (s->i = 0; protoname[s->i]; s->i++) {
if ((char)c != protoname[s->i]) goto no;
crReturn(1);
/*
* Search for a line beginning with the protocol name prefix in
* the input.
*/
s->i = 0;
while (1) {
char prefix[sizeof(protoname)-1];
/*
* Every time round this loop, we're at the start of a new
* line, so look for the prefix.
*/
crMaybeWaitUntilV(
bufchain_size(&ssh->incoming_data) >= sizeof(prefix));
bufchain_fetch(&ssh->incoming_data, prefix, sizeof(prefix));
if (!memcmp(prefix, protoname, sizeof(prefix))) {
bufchain_consume(&ssh->incoming_data, sizeof(prefix));
break;
}
/*
* If we didn't find it, consume data until we see a newline.
*/
while (1) {
int len;
void *data;
char *nl;
crMaybeWaitUntilV(bufchain_size(&ssh->incoming_data) > 0);
bufchain_prefix(&ssh->incoming_data, &data, &len);
if ((nl = memchr(data, '\012', len)) != NULL) {
bufchain_consume(&ssh->incoming_data, nl - (char *)data + 1);
break;
} else {
bufchain_consume(&ssh->incoming_data, len);
}
}
break;
no:
while (c != '\012')
crReturn(1);
crReturn(1);
}
/*
* Now read the rest of the greeting line.
*/
s->vstrsize = sizeof(protoname) + 16;
s->vstring = snewn(s->vstrsize, char);
strcpy(s->vstring, protoname);
s->vslen = strlen(protoname);
s->i = 0;
while (1) {
if (s->vslen >= s->vstrsize - 1) {
s->vstrsize += 16;
do {
int len;
void *data;
char *nl;
crMaybeWaitUntilV(bufchain_size(&ssh->incoming_data) > 0);
bufchain_prefix(&ssh->incoming_data, &data, &len);
if ((nl = memchr(data, '\012', len)) != NULL) {
len = nl - (char *)data + 1;
}
if (s->vslen + len >= s->vstrsize - 1) {
s->vstrsize = (s->vslen + len) * 5 / 4 + 32;
s->vstring = sresize(s->vstring, s->vstrsize, char);
}
s->vstring[s->vslen++] = c;
if (s->i >= 0) {
if (c == '-') {
s->version[s->i] = '\0';
s->i = -1;
} else if (s->i < sizeof(s->version) - 1)
s->version[s->i++] = c;
} else if (c == '\012')
break;
crReturn(1); /* get another char */
}
memcpy(s->vstring + s->vslen, data, len);
s->vslen += len;
bufchain_consume(&ssh->incoming_data, len);
} while (s->vstring[s->vslen-1] != '\012');
s->vstring[s->vslen] = 0;
s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
ssh->agentfwd_enabled = FALSE;
ssh->rdpkt2_bare_state.incoming_sequence = 0;
s->vstring[s->vslen] = 0;
s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
logeventf(ssh, "Server version: %s", s->vstring);
ssh_detect_bugs(ssh, s->vstring);
@ -3447,13 +3479,17 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
* Decide which SSH protocol version to support. This is easy in
* bare ssh-connection mode: only 2.0 is legal.
*/
s->version = dupprintf(
"%.*s", (int)strcspn(s->vstring + strlen(protoname), "-"),
s->vstring + strlen(protoname));
if (ssh_versioncmp(s->version, "2.0") < 0) {
bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol"));
crStop(0);
crStopV;
}
if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode"));
crStop(0);
crStopV;
}
ssh->version = 2;
@ -3463,12 +3499,15 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
/* Send the version string, if we haven't already */
ssh_send_verstring(ssh, protoname, s->version);
sfree(s->version);
/*
* Initialise bare connection protocol.
*/
ssh->protocol = ssh2_bare_connection_protocol;
ssh2_bare_connection_protocol_setup(ssh);
ssh->s_rdpkt = ssh2_bare_connection_rdpkt;
ssh->current_incoming_data_fn = ssh2_bare_connection_rdpkt;
queue_idempotent_callback(&ssh->incoming_data_consumer);
update_specials_menu(ssh->frontend);
ssh->state = SSH_STATE_BEFORE_SIZE;
@ -3481,46 +3520,7 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
sfree(s->vstring);
crFinish(0);
}
static void ssh_process_incoming_data(Ssh ssh,
const unsigned char **data, int *datalen)
{
struct Packet *pktin;
ssh->s_rdpkt(ssh, data, datalen);
while ((pktin = pq_pop(&ssh->pq_full)) != NULL) {
ssh->protocol(ssh, NULL, 0, pktin);
ssh_unref_packet(pktin);
}
}
static void ssh_queue_incoming_data(Ssh ssh,
const unsigned char **data, int *datalen)
{
bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
*data += *datalen;
*datalen = 0;
}
static void ssh_process_queued_incoming_data(Ssh ssh)
{
void *vdata;
const unsigned char *data;
int len, origlen;
while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len);
data = vdata;
origlen = len;
while (!ssh->frozen && len > 0)
ssh_process_incoming_data(ssh, &data, &len);
if (origlen > len)
bufchain_consume(&ssh->queued_incoming_data, origlen - len);
}
crFinishV;
}
static void ssh_set_frozen(Ssh ssh, int frozen)
@ -3530,6 +3530,43 @@ static void ssh_set_frozen(Ssh ssh, int frozen)
ssh->frozen = frozen;
}
static void ssh_process_incoming_data(void *ctx)
{
Ssh ssh = (Ssh)ctx;
if (ssh->state == SSH_STATE_CLOSED)
return;
if (!ssh->frozen && !ssh->pending_newkeys)
ssh->current_incoming_data_fn(ssh);
if (ssh->state == SSH_STATE_CLOSED) /* yes, check _again_ */
return;
if (ssh->incoming_data_seen_eof) {
int need_notify = ssh_do_close(ssh, FALSE);
const char *error_msg = ssh->incoming_data_eof_message;
if (!error_msg) {
if (!ssh->close_expected)
error_msg = "Server unexpectedly closed network connection";
else
error_msg = "Server closed network connection";
}
if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
ssh->exitcode = 0;
if (need_notify)
notify_remote_exit(ssh->frontend);
if (error_msg)
logevent(error_msg);
if (!ssh->close_expected || !ssh->clean_exit)
connection_fatal(ssh->frontend, "%s", error_msg);
}
}
static void ssh_process_pq_full(void *ctx)
{
Ssh ssh = (Ssh)ctx;
@ -3541,66 +3578,6 @@ static void ssh_process_pq_full(void *ctx)
}
}
static void ssh_gotdata(Ssh ssh, const unsigned char *data, int datalen)
{
/* Log raw data, if we're in that mode. */
if (ssh->logctx)
log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen,
0, NULL, NULL, 0, NULL);
crBegin(ssh->ssh_gotdata_crstate);
/*
* To begin with, feed the characters one by one to the
* protocol initialisation / selection function do_ssh_init().
* When that returns 0, we're done with the initial greeting
* exchange and can move on to packet discipline.
*/
while (1) {
int ret; /* need not be kept across crReturn */
if (datalen == 0)
crReturnV; /* more data please */
ret = ssh->do_ssh_init(ssh, *data);
data++;
datalen--;
if (ret == 0)
break;
}
/*
* We emerge from that loop when the initial negotiation is
* over and we have selected an s_rdpkt function. Now pass
* everything to s_rdpkt, and then pass the resulting packets
* to the proper protocol handler.
*/
while (1) {
while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) {
if (ssh->frozen) {
ssh_queue_incoming_data(ssh, &data, &datalen);
/* This uses up all data and cannot cause anything interesting
* to happen; indeed, for anything to happen at all, we must
* return, so break out. */
break;
} else if (bufchain_size(&ssh->queued_incoming_data) > 0) {
/* This uses up some or all data, and may freeze the
* session. */
ssh_process_queued_incoming_data(ssh);
} else {
/* This uses up some or all data, and may freeze the
* session. */
ssh_process_incoming_data(ssh, &data, &datalen);
}
/* FIXME this is probably EBW. */
if (ssh->state == SSH_STATE_CLOSED)
return;
}
/* We're out of data. Go and get some more. */
crReturnV;
}
crFinishV;
}
static int ssh_do_close(Ssh ssh, int notify_exit)
{
int ret = 0;
@ -3714,31 +3691,23 @@ static void ssh_closing(Plug plug, const char *error_msg, int error_code,
int calling_back)
{
Ssh ssh = (Ssh) plug;
int need_notify = ssh_do_close(ssh, FALSE);
if (!error_msg) {
if (!ssh->close_expected)
error_msg = "Server unexpectedly closed network connection";
else
error_msg = "Server closed network connection";
}
if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
ssh->exitcode = 0;
if (need_notify)
notify_remote_exit(ssh->frontend);
if (error_msg)
logevent(error_msg);
if (!ssh->close_expected || !ssh->clean_exit)
connection_fatal(ssh->frontend, "%s", error_msg);
ssh->incoming_data_seen_eof = TRUE;
ssh->incoming_data_eof_message = dupstr(error_msg);
queue_idempotent_callback(&ssh->incoming_data_consumer);
}
static void ssh_receive(Plug plug, int urgent, char *data, int len)
{
Ssh ssh = (Ssh) plug;
ssh_gotdata(ssh, (unsigned char *)data, len);
/* Log raw data, if we're in that mode. */
if (ssh->logctx)
log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len,
0, NULL, NULL, 0, NULL);
bufchain_add(&ssh->incoming_data, data, len);
queue_idempotent_callback(&ssh->incoming_data_consumer);
if (ssh->state == SSH_STATE_CLOSED) {
ssh_do_close(ssh, TRUE);
}
@ -3853,14 +3822,14 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port,
* We are a downstream.
*/
ssh->bare_connection = TRUE;
ssh->do_ssh_init = do_ssh_connection_init;
ssh->current_incoming_data_fn = do_ssh_connection_init;
ssh->fullhostname = NULL;
*realhost = dupstr(host); /* best we can do */
} else {
/*
* We're not a downstream, so open a normal socket.
*/
ssh->do_ssh_init = do_ssh_init;
ssh->current_incoming_data_fn = do_ssh_init;
/*
* Try to find host.
@ -3995,10 +3964,10 @@ static void ssh_dialog_callback(void *sshv, int ret)
do_ssh2_transport(ssh, NULL, -1, NULL);
/*
* This may have unfrozen the SSH connection, so do a
* queued-data run.
* This may have unfrozen the SSH connection.
*/
ssh_process_queued_incoming_data(ssh);
if (!ssh->frozen)
queue_idempotent_callback(&ssh->incoming_data_consumer);
}
static void ssh_agentf_got_response(struct ssh_channel *c,
@ -4088,8 +4057,7 @@ static void ssh_agentf_try_forward(struct ssh_channel *c)
messagelen = lengthfield + 4;
message = snewn(messagelen, unsigned char);
bufchain_fetch(&c->u.a.inbuffer, message, messagelen);
bufchain_consume(&c->u.a.inbuffer, messagelen);
bufchain_fetch_consume(&c->u.a.inbuffer, message, messagelen);
c->u.a.pending = agent_query(
message, messagelen, &reply, &replylen, ssh_agentf_callback, c);
sfree(message);
@ -12353,13 +12321,18 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->ssh1_rdpkt_crstate = 0;
ssh->ssh2_rdpkt_crstate = 0;
ssh->ssh2_bare_rdpkt_crstate = 0;
ssh->ssh_gotdata_crstate = 0;
ssh->do_ssh1_connection_crstate = 0;
ssh->do_ssh_init_state = NULL;
ssh->do_ssh_connection_init_state = NULL;
ssh->do_ssh1_login_state = NULL;
ssh->do_ssh2_transport_state = NULL;
ssh->do_ssh2_authconn_state = NULL;
bufchain_init(&ssh->incoming_data);
ssh->incoming_data_seen_eof = FALSE;
ssh->incoming_data_eof_message = NULL;
ssh->incoming_data_consumer.fn = ssh_process_incoming_data;
ssh->incoming_data_consumer.ctx = ssh;
ssh->incoming_data_consumer.queued = FALSE;
pq_init(&ssh->pq_full);
ssh->pq_full_consumer.fn = ssh_process_pq_full;
ssh->pq_full_consumer.ctx = ssh;
@ -12375,7 +12348,6 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->queueing = FALSE;
ssh->qhead = ssh->qtail = NULL;
ssh->deferred_rekey_reason = NULL;
bufchain_init(&ssh->queued_incoming_data);
ssh->frozen = FALSE;
ssh->username = NULL;
ssh->sent_console_eof = FALSE;
@ -12538,6 +12510,8 @@ static void ssh_free(void *handle)
sfree(ssh->do_ssh1_login_state);
sfree(ssh->do_ssh2_transport_state);
sfree(ssh->do_ssh2_authconn_state);
bufchain_clear(&ssh->incoming_data);
sfree(ssh->incoming_data_eof_message);
pq_clear(&ssh->pq_full);
sfree(ssh->v_c);
sfree(ssh->v_s);
@ -12553,7 +12527,6 @@ static void ssh_free(void *handle)
expire_timer_context(ssh);
if (ssh->pinger)
pinger_free(ssh->pinger);
bufchain_clear(&ssh->queued_incoming_data);
sfree(ssh->username);
conf_free(ssh->conf);
@ -12992,7 +12965,7 @@ static void ssh_unthrottle(void *handle, int bufsize)
* Now process any SSH connection data that was stashed in our
* queue while we were frozen.
*/
ssh_process_queued_incoming_data(ssh);
queue_idempotent_callback(&ssh->incoming_data_consumer);
}
void ssh_send_port_open(void *channel, const char *hostname, int port,