#include <signal.h>
#include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
#include "tree234.h"
#include "storage.h"
#include "ssh.h"
struct Packet *pktin);
static void ssh_channel_init(struct ssh_channel *c);
static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin);
+static void ssh_channel_got_eof(struct ssh_channel *c);
static void ssh2_channel_check_close(struct ssh_channel *c);
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason);
static void ssh_channel_destroy(struct ssh_channel *c);
static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize);
static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
} v;
union {
struct ssh_agent_channel {
- unsigned char *message;
- unsigned char msglen[4];
- unsigned lensofar, totallen;
- int outstanding_requests;
+ bufchain inbuffer;
+ agent_pending_query *pending;
} a;
struct ssh_x11_channel {
struct X11Connection *xconn;
* with a newly cross-certified host key.
*/
int cross_certifying;
+
+ /*
+ * Any asynchronous query to our SSH agent that we might have in
+ * flight from the main authentication loop. (Queries from
+ * agent-forwarding channels live in their channel structure.)
+ */
+ agent_pending_query *auth_agent_query;
};
static const char *ssh_pkt_type(Ssh ssh, int type)
*/
if (ssh->channels) {
while (NULL != (c = index234(ssh->channels, 0))) {
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.xconn);
- break;
- case CHAN_SOCKDATA:
- pfd_close(c->u.pfd.pf);
- break;
- }
+ ssh_channel_close_local(c, NULL);
del234(ssh->channels, c); /* moving next one to index 0 */
if (ssh->version == 2)
bufchain_clear(&c->v.v2.outbuffer);
}
}
+static void ssh_agentf_try_forward(struct ssh_channel *c);
+
/*
* Throttle or unthrottle _all_ local data streams (for when sends
* on the SSH connection itself back up).
x11_override_throttle(c->u.x11.xconn, enable);
break;
case CHAN_AGENT:
- /* Agent channels require no buffer management. */
+ /* Agent forwarding channels are buffer-managed by
+ * checking ssh->throttled_all in ssh_agentf_try_forward.
+ * So at the moment we _un_throttle again, we must make an
+ * attempt to do something. */
+ if (!enable)
+ ssh_agentf_try_forward(c);
break;
case CHAN_SOCKDATA:
pfd_override_throttle(c->u.pfd.pf, enable);
{
Ssh ssh = (Ssh) sshv;
+ ssh->auth_agent_query = NULL;
+
ssh->agent_response = reply;
ssh->agent_response_len = replylen;
ssh_process_queued_incoming_data(ssh);
}
-static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+static void ssh_agentf_got_response(struct ssh_channel *c,
+ void *reply, int replylen)
{
- struct ssh_channel *c = (struct ssh_channel *)cv;
- const void *sentreply = reply;
+ c->u.a.pending = NULL;
+
+ assert(!(c->closes & CLOSES_SENT_EOF));
- c->u.a.outstanding_requests--;
- if (!sentreply) {
- /* Fake SSH_AGENT_FAILURE. */
- sentreply = "\0\0\0\1\5";
+ if (!reply) {
+ /* The real agent didn't send any kind of reply at all for
+ * some reason, so fake an SSH_AGENT_FAILURE. */
+ reply = "\0\0\0\1\5";
replylen = 5;
}
- ssh_send_channel_data(c, sentreply, replylen);
- if (reply)
- sfree(reply);
+
+ ssh_send_channel_data(c, reply, replylen);
+}
+
+static void ssh_agentf_callback(void *cv, void *reply, int replylen);
+
+static void ssh_agentf_try_forward(struct ssh_channel *c)
+{
+ unsigned datalen, lengthfield, messagelen;
+ unsigned char *message;
+ unsigned char msglen[4];
+ void *reply;
+ int replylen;
+
+ /*
+ * Don't try to parallelise agent requests. Wait for each one to
+ * return before attempting the next.
+ */
+ if (c->u.a.pending)
+ return;
+
/*
- * If we've already seen an incoming EOF but haven't sent an
- * outgoing one, this may be the moment to send it.
+ * If the outgoing side of the channel connection is currently
+ * throttled (for any reason, either that channel's window size or
+ * the entire SSH connection being throttled), don't submit any
+ * new forwarded requests to the real agent. This causes the input
+ * side of the agent forwarding not to be emptied, exerting the
+ * required back-pressure on the remote client, and encouraging it
+ * to read our responses before sending too many more requests.
*/
- if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+ if (c->ssh->throttled_all ||
+ (c->ssh->version == 2 && c->v.v2.remwindow == 0))
+ return;
+
+ if (c->closes & CLOSES_SENT_EOF) {
+ /*
+ * If we've already sent outgoing EOF, there's nothing we can
+ * do with incoming data except consume it and throw it away.
+ */
+ bufchain_clear(&c->u.a.inbuffer);
+ return;
+ }
+
+ while (1) {
+ /*
+ * Try to extract a complete message from the input buffer.
+ */
+ datalen = bufchain_size(&c->u.a.inbuffer);
+ if (datalen < 4)
+ break; /* not even a length field available yet */
+
+ bufchain_fetch(&c->u.a.inbuffer, msglen, 4);
+ lengthfield = GET_32BIT(msglen);
+
+ if (lengthfield > AGENT_MAX_MSGLEN) {
+ /*
+ * If the remote has sent a message that's just _too_
+ * long, we should reject it in advance of seeing the rest
+ * of the incoming message, and also close the connection
+ * for good measure (which avoids us having to faff about
+ * with carefully ignoring just the right number of bytes
+ * from the overlong message).
+ */
+ ssh_agentf_got_response(c, NULL, 0);
+ sshfwd_write_eof(c);
+ return;
+ }
+
+ if (lengthfield > datalen - 4)
+ break; /* a whole message is not yet available */
+
+ messagelen = lengthfield + 4;
+
+ message = snewn(messagelen, unsigned char);
+ bufchain_fetch(&c->u.a.inbuffer, message, messagelen);
+ bufchain_consume(&c->u.a.inbuffer, messagelen);
+ c->u.a.pending = agent_query(
+ message, messagelen, &reply, &replylen, ssh_agentf_callback, c);
+ sfree(message);
+
+ if (c->u.a.pending)
+ return; /* agent_query promised to reply in due course */
+
+ /*
+ * If the agent gave us an answer immediately, pass it
+ * straight on and go round this loop again.
+ */
+ ssh_agentf_got_response(c, reply, replylen);
+ sfree(reply);
+ }
+
+ /*
+ * If we get here (i.e. we left the above while loop via 'break'
+ * rather than 'return'), that means we've determined that the
+ * input buffer for the agent forwarding connection doesn't
+ * contain a complete request.
+ *
+ * So if there's potentially more data to come, we can return now,
+ * and wait for the remote client to send it. But if the remote
+ * has sent EOF, it would be a mistake to do that, because we'd be
+ * waiting a long time. So this is the moment to check for EOF,
+ * and respond appropriately.
+ */
+ if (c->closes & CLOSES_RCVD_EOF)
sshfwd_write_eof(c);
}
+static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+{
+ struct ssh_channel *c = (struct ssh_channel *)cv;
+
+ ssh_agentf_got_response(c, reply, replylen);
+ sfree(reply);
+
+ /*
+ * Now try to extract and send further messages from the channel's
+ * input-side buffer.
+ */
+ ssh_agentf_try_forward(c);
+}
+
/*
* Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
* non-NULL, otherwise just close the connection. `client_reason' == NULL
/* Request the keys held by the agent. */
PUT_32BIT(s->request, 1);
s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
- if (!agent_query(s->request, 5, &r, &s->responselen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->request, 5, &r, &s->responselen, ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturn(0);
if (pktin) {
memcpy(q, s->session_id, 16);
q += 16;
PUT_32BIT(q, 1); /* response format */
- if (!agent_query(agentreq, len + 4, &vret, &retlen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ agentreq, len + 4, &vret, &retlen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
sfree(agentreq);
do {
crReturn(0);
void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
{
Ssh ssh = c->ssh;
+ char *reason;
if (ssh->state == SSH_STATE_CLOSED)
return;
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.xconn);
- logeventf(ssh, "Forwarded X11 connection terminated due to local "
- "error: %s", err);
- break;
- case CHAN_SOCKDATA:
- pfd_close(c->u.pfd.pf);
- logeventf(ssh, "Forwarded port closed due to local error: %s", err);
- break;
- }
- c->type = CHAN_ZOMBIE;
+ reason = dupprintf("due to local error: %s", err);
+ ssh_channel_close_local(c, reason);
+ sfree(reason);
c->pending_eof = FALSE; /* this will confuse a zombie channel */
ssh2_channel_check_close(c);
c->remoteid = remoteid;
c->halfopen = FALSE;
c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- c->u.a.message = NULL;
- c->u.a.outstanding_requests = 0;
+ c->u.a.pending = NULL;
+ bufchain_init(&c->u.a.inbuffer);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
PKT_INT, c->remoteid, PKT_INT, c->localid,
PKT_END);
c = ssh_channel_msg(ssh, pktin);
if (c) {
- if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
- !(c->closes & CLOSES_RCVD_EOF)) {
+ if (pktin->type == SSH1_MSG_CHANNEL_CLOSE) {
/*
* Received CHANNEL_CLOSE, which we translate into
* outgoing EOF.
*/
- int send_close = FALSE;
-
- c->closes |= CLOSES_RCVD_EOF;
-
- switch (c->type) {
- case CHAN_X11:
- assert(c->u.x11.xconn != NULL);
- x11_send_eof(c->u.x11.xconn);
- break;
- case CHAN_SOCKDATA:
- assert(c->u.pfd.pf != NULL);
- pfd_send_eof(c->u.pfd.pf);
- break;
- case CHAN_AGENT:
- send_close = TRUE;
- break;
- }
-
- if (send_close && !(c->closes & CLOSES_SENT_EOF)) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- c->closes |= CLOSES_SENT_EOF;
- }
+ ssh_channel_got_eof(c);
}
if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION &&
static int ssh_agent_channel_data(struct ssh_channel *c, char *data,
int length)
{
- while (length > 0) {
- if (c->u.a.lensofar < 4) {
- unsigned int l = min(4 - c->u.a.lensofar, (unsigned)length);
- memcpy(c->u.a.msglen + c->u.a.lensofar, data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == 4) {
- c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
- c->u.a.message = snewn(c->u.a.totallen, unsigned char);
- memcpy(c->u.a.message, c->u.a.msglen, 4);
- }
- if (c->u.a.lensofar >= 4 && length > 0) {
- unsigned int l = min(c->u.a.totallen - c->u.a.lensofar,
- (unsigned)length);
- memcpy(c->u.a.message + c->u.a.lensofar, data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == c->u.a.totallen) {
- void *reply;
- int replylen;
- c->u.a.outstanding_requests++;
- if (agent_query(c->u.a.message, c->u.a.totallen, &reply, &replylen,
- ssh_agentf_callback, c))
- ssh_agentf_callback(c, reply, replylen);
- sfree(c->u.a.message);
- c->u.a.message = NULL;
- c->u.a.lensofar = 0;
- }
- }
- return 0; /* agent channels never back up */
+ bufchain_add(&c->u.a.inbuffer, data, length);
+ ssh_agentf_try_forward(c);
+
+ /*
+ * We exert back-pressure on an agent forwarding client if and
+ * only if we're waiting for the response to an asynchronous agent
+ * request. This prevents the client running out of window while
+ * receiving the _first_ message, but means that if any message
+ * takes time to process, the client will be discouraged from
+ * sending an endless stream of further ones after it.
+ */
+ return (c->u.a.pending ? bufchain_size(&c->u.a.inbuffer) : 0);
}
static int ssh_channel_data(struct ssh_channel *c, int is_stderr,
s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
logevent("Storing additional host key for this host:");
logevent(s->fingerprint);
+ sfree(s->fingerprint);
store_host_key(ssh->savedhost, ssh->savedport,
ssh->hostkey->keytype, s->keystr);
ssh->cross_certifying = FALSE;
x11_unthrottle(c->u.x11.xconn);
break;
case CHAN_AGENT:
- /* agent sockets are request/response and need no
- * buffer management */
+ /* Now that we've successfully sent all the outgoing
+ * replies we had, try to process more incoming data. */
+ ssh_agentf_try_forward(c);
break;
case CHAN_SOCKDATA:
pfd_unthrottle(c->u.pfd.pf);
sfree(buf);
}
-static void ssh_channel_destroy(struct ssh_channel *c)
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel. This converts the channel into a CHAN_ZOMBIE.
+ */
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason)
{
Ssh ssh = c->ssh;
+ char const *msg = NULL;
switch (c->type) {
case CHAN_MAINSESSION:
case CHAN_X11:
assert(c->u.x11.xconn != NULL);
x11_close(c->u.x11.xconn);
- logevent("Forwarded X11 connection terminated");
+ msg = "Forwarded X11 connection terminated";
break;
case CHAN_AGENT:
- sfree(c->u.a.message);
+ if (c->u.a.pending)
+ agent_cancel_query(c->u.a.pending);
+ bufchain_clear(&c->u.a.inbuffer);
+ msg = "Agent-forwarding connection closed";
break;
case CHAN_SOCKDATA:
assert(c->u.pfd.pf != NULL);
pfd_close(c->u.pfd.pf);
- logevent("Forwarded port closed");
+ msg = "Forwarded port closed";
break;
}
+ c->type = CHAN_ZOMBIE;
+ if (msg != NULL) {
+ if (reason != NULL)
+ logeventf(ssh, "%s %s", msg, reason);
+ else
+ logevent(msg);
+ }
+}
+
+static void ssh_channel_destroy(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ ssh_channel_close_local(c, NULL);
del234(ssh->channels, c);
if (ssh->version == 2) {
}
}
-static void ssh2_channel_got_eof(struct ssh_channel *c)
+static void ssh_channel_got_eof(struct ssh_channel *c)
{
if (c->closes & CLOSES_RCVD_EOF)
return; /* already seen EOF */
c->closes |= CLOSES_RCVD_EOF;
if (c->type == CHAN_X11) {
+ assert(c->u.x11.xconn != NULL);
x11_send_eof(c->u.x11.xconn);
} else if (c->type == CHAN_AGENT) {
- if (c->u.a.outstanding_requests == 0) {
- /* Manufacture an outgoing EOF in response to the incoming one. */
- sshfwd_write_eof(c);
- }
+ /* Just call try_forward, which will respond to the EOF now if
+ * appropriate, or wait until the queue of outstanding
+ * requests is dealt with if not */
+ ssh_agentf_try_forward(c);
} else if (c->type == CHAN_SOCKDATA) {
+ assert(c->u.pfd.pf != NULL);
pfd_send_eof(c->u.pfd.pf);
} else if (c->type == CHAN_MAINSESSION) {
Ssh ssh = c->ssh;
c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- ssh2_channel_got_eof(c);
+ ssh_channel_got_eof(c);
ssh2_channel_check_close(c);
}
* When we receive CLOSE on a channel, we assume it comes with an
* implied EOF if we haven't seen EOF yet.
*/
- ssh2_channel_got_eof(c);
+ ssh_channel_got_eof(c);
if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
/*
error = "Agent forwarding is not enabled";
else {
c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- c->u.a.message = NULL;
- c->u.a.outstanding_requests = 0;
+ bufchain_init(&c->u.a.inbuffer);
+ c->u.a.pending = NULL;
}
} else {
error = "Unsupported channel type requested";
/* Request the keys held by the agent. */
PUT_32BIT(s->agent_request, 1);
s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
- if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->agent_request, 5, &r, &s->agent_responselen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturnV;
if (pktin) {
goto done_agent_query;
}
bloblen = toint(GET_32BIT(q));
+ lenleft -= 4;
+ q += 4;
if (bloblen < 0 || bloblen > lenleft) {
logeventf(ssh, "Pageant response was truncated");
s->nkeys = 0;
goto done_agent_query;
}
- lenleft -= 4 + bloblen;
- q += 4 + bloblen;
+ lenleft -= bloblen;
+ q += bloblen;
commentlen = toint(GET_32BIT(q));
+ lenleft -= 4;
+ q += 4;
if (commentlen < 0 || commentlen > lenleft) {
logeventf(ssh, "Pageant response was truncated");
s->nkeys = 0;
goto done_agent_query;
}
- lenleft -= 4 + commentlen;
- q += 4 + commentlen;
+ lenleft -= commentlen;
+ q += commentlen;
}
}
s->q += s->pktout->length - 5;
/* And finally the (zero) flags word. */
PUT_32BIT(s->q, 0);
- if (!agent_query(s->agentreq, s->len + 4,
- &vret, &s->retlen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->agentreq, s->len + 4, &vret, &s->retlen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturnV;
if (pktin) {
CONF_ssh_rekey_data));
ssh->kex_in_progress = FALSE;
+ ssh->auth_agent_query = NULL;
+
#ifndef NO_GSSAPI
ssh->gsslibs = NULL;
#endif
if (ssh->channels) {
while ((c = delpos234(ssh->channels, 0)) != NULL) {
- switch (c->type) {
- case CHAN_X11:
- assert(c->u.x11.xconn != NULL);
- x11_close(c->u.x11.xconn);
- break;
- case CHAN_SOCKDATA:
- assert(c->u.pfd.pf != NULL);
- pfd_close(c->u.pfd.pf);
- break;
- }
+ ssh_channel_close_local(c, NULL);
if (ssh->version == 2) {
struct outstanding_channel_request *ocr, *nocr;
ocr = c->v.v2.chanreq_head;
bufchain_clear(&ssh->queued_incoming_data);
sfree(ssh->username);
conf_free(ssh->conf);
+
+ if (ssh->auth_agent_query)
+ agent_cancel_query(ssh->auth_agent_query);
+
#ifndef NO_GSSAPI
if (ssh->gsslibs)
ssh_gss_cleanup(ssh->gsslibs);