X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=931d8686b6f18c9f8090397fc856f53e7a0a54b7;hb=ca8876f0044c2eb7dbc5a61a432e799973e7e51c;hp=15c53a6204d2caaa70ad2fccefa3089aa20af35a;hpb=4115ab6e2e3ddd22eda230fc7b2c27781064d38a;p=PuTTY.git diff --git a/ssh.c b/ssh.c index 15c53a62..931d8686 100644 --- a/ssh.c +++ b/ssh.c @@ -10,6 +10,7 @@ #include #include "putty.h" +#include "pageant.h" /* for AGENT_MAX_MSGLEN */ #include "tree234.h" #include "storage.h" #include "ssh.h" @@ -366,7 +367,9 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, 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); @@ -571,10 +574,8 @@ struct ssh_channel { } 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; @@ -983,6 +984,13 @@ struct ssh_tag { * 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) @@ -3463,14 +3471,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit) */ 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); @@ -3777,6 +3778,8 @@ static void ssh_throttle_conn(Ssh ssh, int adjust) } } +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). @@ -3803,7 +3806,12 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) 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); @@ -3816,6 +3824,8 @@ static void ssh_agent_callback(void *sshv, void *reply, int replylen) { Ssh ssh = (Ssh) sshv; + ssh->auth_agent_query = NULL; + ssh->agent_response = reply; ssh->agent_response_len = replylen; @@ -3843,28 +3853,139 @@ static void ssh_dialog_callback(void *sshv, int ret) 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); + } + + /* + * 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 @@ -4367,8 +4488,9 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, /* 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) { @@ -4473,8 +4595,10 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, 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); @@ -5001,22 +5125,14 @@ void sshfwd_write_eof(struct ssh_channel *c) 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); @@ -5552,9 +5668,8 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) 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); @@ -5657,39 +5772,12 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) 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: - if (c->u.x11.xconn) - x11_send_eof(c->u.x11.xconn); - else - send_close = TRUE; - break; - case CHAN_SOCKDATA: - if (c->u.pfd.pf) - pfd_send_eof(c->u.pfd.pf); - else - send_close = TRUE; - 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 && @@ -5722,40 +5810,18 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) 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, @@ -7363,6 +7429,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, 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; @@ -7755,8 +7822,9 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) 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); @@ -8160,9 +8228,14 @@ void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...) 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: @@ -8170,19 +8243,36 @@ static void ssh_channel_destroy(struct ssh_channel *c) update_specials_menu(ssh->frontend); break; case CHAN_X11: - if (c->u.x11.xconn != NULL) - x11_close(c->u.x11.xconn); - logevent("Forwarded X11 connection terminated"); + assert(c->u.x11.xconn != NULL); + x11_close(c->u.x11.xconn); + 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: - if (c->u.pfd.pf != NULL) - pfd_close(c->u.pfd.pf); - logevent("Forwarded port closed"); + assert(c->u.pfd.pf != NULL); + pfd_close(c->u.pfd.pf); + 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) { @@ -8239,20 +8329,22 @@ static void ssh2_channel_check_close(struct ssh_channel *c) } } -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; @@ -8271,8 +8363,6 @@ static void ssh2_channel_got_eof(struct ssh_channel *c) } ssh->sent_console_eof = TRUE; } - - ssh2_channel_check_close(c); } static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) @@ -8282,7 +8372,8 @@ static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) c = ssh_channel_msg(ssh, pktin); if (!c) return; - ssh2_channel_got_eof(c); + ssh_channel_got_eof(c); + ssh2_channel_check_close(c); } static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) @@ -8297,7 +8388,7 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) * 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)) { /* @@ -8376,8 +8467,8 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); if (c->type == CHAN_SOCKDATA) { - if (c->u.pfd.pf) - pfd_confirm(c->u.pfd.pf); + assert(c->u.pfd.pf != NULL); + pfd_confirm(c->u.pfd.pf); } else if (c->type == CHAN_ZOMBIE) { /* * This case can occur if a local socket error occurred @@ -8805,9 +8896,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) 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"; @@ -9310,8 +9400,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, /* 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) { @@ -9749,9 +9841,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, 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) { @@ -11187,6 +11280,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, CONF_ssh_rekey_data)); ssh->kex_in_progress = FALSE; + ssh->auth_agent_query = NULL; + #ifndef NO_GSSAPI ssh->gsslibs = NULL; #endif @@ -11248,16 +11343,7 @@ static void ssh_free(void *handle) if (ssh->channels) { while ((c = delpos234(ssh->channels, 0)) != NULL) { - switch (c->type) { - case CHAN_X11: - if (c->u.x11.xconn != NULL) - x11_close(c->u.x11.xconn); - break; - case CHAN_SOCKDATA: - if (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; @@ -11311,6 +11397,10 @@ static void ssh_free(void *handle) 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);