X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/78280721237452736c89a89866380e0673f29cb3..ab18ccfffb5274281362d41829fa924f0a964168:/ssh.c diff --git a/ssh.c b/ssh.c index 82082466..95fa2f2c 100644 --- a/ssh.c +++ b/ssh.c @@ -544,7 +544,7 @@ static int ssh_comp_none_disable(void *handle) return 0; } const static struct ssh_compress ssh_comp_none = { - "none", + "none", NULL, ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, ssh_comp_none_disable, NULL @@ -941,6 +941,13 @@ struct ssh_tag { * Fully qualified host name, which we need if doing GSSAPI. */ char *fullhostname; + +#ifndef NO_GSSAPI + /* + * GSSAPI libraries for this session. + */ + struct ssh_gss_liblist *gsslibs; +#endif }; #define logevent(s) logevent(ssh->frontend, s) @@ -2855,6 +2862,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit) x11_close(c->u.x11.s); break; case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: pfd_close(c->u.pfd.s); break; } @@ -4164,7 +4172,7 @@ void sshfwd_close(struct ssh_channel *c) if (ssh->state == SSH_STATE_CLOSED) return; - if (c && !c->closes) { + if (!c->closes) { /* * If halfopen is true, we have sent * CHANNEL_OPEN for this channel, but it hasn't even been @@ -4350,6 +4358,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx) rpf = del234(ssh->rportfwds, pf); assert(rpf == pf); + pf->pfrec->remote = NULL; free_rportfwd(pf); } } @@ -4526,6 +4535,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg) logeventf(ssh, "Cancelling %s", message); sfree(message); + /* epf->remote or epf->local may be NULL if setting up a + * forwarding failed. */ if (epf->remote) { struct ssh_rportfwd *rpf = epf->remote; struct Packet *pktout; @@ -5411,6 +5422,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, int n_preferred_ciphers; const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; const struct ssh_compress *preferred_comp; + int userauth_succeeded; /* for delayed compression */ + int pending_compression; int got_session_id, activated_authconn; struct Packet *pktout; int dlgret; @@ -5426,6 +5439,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, s->cscomp_tobe = s->sccomp_tobe = NULL; s->got_session_id = s->activated_authconn = FALSE; + s->userauth_succeeded = FALSE; + s->pending_compression = FALSE; /* * Be prepared to work around the buggy MAC problem. @@ -5590,26 +5605,32 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, if (i < s->nmacs - 1) ssh2_pkt_addstring_str(s->pktout, ","); } - /* List client->server compression algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - assert(lenof(compressions) > 1); - ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); - for (i = 0; i < lenof(compressions); i++) { - const struct ssh_compress *c = compressions[i]; - if (c != s->preferred_comp) { + /* List client->server compression algorithms, + * then server->client compression algorithms. (We use the + * same set twice.) */ + for (j = 0; j < 2; j++) { + ssh2_pkt_addstring_start(s->pktout); + assert(lenof(compressions) > 1); + /* Prefer non-delayed versions */ + ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); + /* We don't even list delayed versions of algorithms until + * they're allowed to be used, to avoid a race. See the end of + * this function. */ + if (s->userauth_succeeded && s->preferred_comp->delayed_name) { ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->name); + ssh2_pkt_addstring_str(s->pktout, + s->preferred_comp->delayed_name); } - } - /* List server->client compression algorithms. */ - ssh2_pkt_addstring_start(s->pktout); - assert(lenof(compressions) > 1); - ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); - for (i = 0; i < lenof(compressions); i++) { - const struct ssh_compress *c = compressions[i]; - if (c != s->preferred_comp) { - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->name); + for (i = 0; i < lenof(compressions); i++) { + const struct ssh_compress *c = compressions[i]; + if (c != s->preferred_comp) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->name); + if (s->userauth_succeeded && c->delayed_name) { + ssh2_pkt_addstring_str(s->pktout, ","); + ssh2_pkt_addstring_str(s->pktout, c->delayed_name); + } + } } } /* List client->server languages. Empty list. */ @@ -5758,6 +5779,13 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, if (in_commasep_string(c->name, str, len)) { s->cscomp_tobe = c; break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->cscomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } } } ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */ @@ -5767,8 +5795,19 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, if (in_commasep_string(c->name, str, len)) { s->sccomp_tobe = c; break; + } else if (in_commasep_string(c->delayed_name, str, len)) { + if (s->userauth_succeeded) { + s->sccomp_tobe = c; + break; + } else { + s->pending_compression = TRUE; /* try this later */ + } } } + if (s->pending_compression) { + logevent("Server supports delayed compression; " + "will try this later"); + } ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; @@ -6304,19 +6343,52 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * start. * * We _also_ go back to the start if we see pktin==NULL and - * inlen==-1, because this is a special signal meaning + * inlen negative, because this is a special signal meaning * `initiate client-driven rekey', and `in' contains a message * giving the reason for the rekey. + * + * inlen==-1 means always initiate a rekey; + * inlen==-2 means that userauth has completed successfully and + * we should consider rekeying (for delayed compression). */ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || - (!pktin && inlen == -1))) { + (!pktin && inlen < 0))) { wait_for_rekey: crReturn(1); } if (pktin) { logevent("Server initiated key re-exchange"); } else { + if (inlen == -2) { + /* + * authconn has seen a USERAUTH_SUCCEEDED. Time to enable + * delayed compression, if it's available. + * + * draft-miller-secsh-compression-delayed-00 says that you + * negotiate delayed compression in the first key exchange, and + * both sides start compressing when the server has sent + * USERAUTH_SUCCESS. This has a race condition -- the server + * can't know when the client has seen it, and thus which incoming + * packets it should treat as compressed. + * + * Instead, we do the initial key exchange without offering the + * delayed methods, but note if the server offers them; when we + * get here, if a delayed method was available that was higher + * on our list than what we got, we initiate a rekey in which we + * _do_ list the delayed methods (and hopefully get it as a + * result). Subsequent rekeys will do the same. + */ + assert(!s->userauth_succeeded); /* should only happen once */ + s->userauth_succeeded = TRUE; + if (!s->pending_compression) + /* Can't see any point rekeying. */ + goto wait_for_rekey; /* this is utterly horrid */ + /* else fall through to rekey... */ + s->pending_compression = FALSE; + } /* + * Now we've decided to rekey. + * * Special case: if the server bug is set that doesn't * allow rekeying, we give a different log message and * continue waiting. (If such a server _initiates_ a rekey, @@ -6334,7 +6406,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, ssh2_timer, ssh); } - goto wait_for_rekey; /* this is utterly horrid */ + goto wait_for_rekey; /* this is still utterly horrid */ } else { logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); } @@ -7184,12 +7256,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) } /* - * Buffer banner messages for later display at some convenient point. + * Buffer banner messages for later display at some convenient point, + * if we're going to display them. */ static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) { /* Arbitrary limit to prevent unbounded inflation of buffer */ - if (bufchain_size(&ssh->banner) <= 131072) { + if (ssh->cfg.ssh_show_banner && + bufchain_size(&ssh->banner) <= 131072) { char *banner = NULL; int size = 0; ssh_pkt_getstring(pktin, &banner, &size); @@ -7243,7 +7317,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int tried_gssapi; #endif int kbd_inter_refused; - int we_are_in; + int we_are_in, userauth_success; prompts_t *cur_prompt; int num_prompts; char username[100]; @@ -7279,7 +7353,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, crBegin(ssh->do_ssh2_authconn_crstate); s->done_service_req = FALSE; - s->we_are_in = FALSE; + s->we_are_in = s->userauth_success = FALSE; #ifndef NO_GSSAPI s->tried_gssapi = FALSE; #endif @@ -7535,6 +7609,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } while (1) { + char *methods = NULL; + int methlen = 0; + /* * Wait for the result of the last authentication request. */ @@ -7566,7 +7643,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { logevent("Access granted"); - s->we_are_in = TRUE; + s->we_are_in = s->userauth_success = TRUE; break; } @@ -7584,8 +7661,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * helpfully try next. */ if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - char *methods; - int methlen; ssh_pkt_getstring(pktin, &methods, &methlen); if (!ssh2_pkt_getbool(pktin)) { /* @@ -7641,11 +7716,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, in_commasep_string("password", methods, methlen); s->can_keyb_inter = ssh->cfg.try_ki_auth && in_commasep_string("keyboard-interactive", methods, methlen); -#ifndef NO_GSSAPI - ssh_gss_init(); +#ifndef NO_GSSAPI + if (!ssh->gsslibs) + ssh->gsslibs = ssh_gss_setup(&ssh->cfg); s->can_gssapi = ssh->cfg.try_gssapi_auth && in_commasep_string("gssapi-with-mic", methods, methlen) && - n_ssh_gss_libraries > 0; + ssh->gsslibs->nlibraries > 0; #endif } @@ -7997,9 +8073,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->gsslib = NULL; for (i = 0; i < ngsslibs; i++) { int want_id = ssh->cfg.ssh_gsslist[i]; - for (j = 0; j < n_ssh_gss_libraries; j++) - if (ssh_gss_libraries[j].id == want_id) { - s->gsslib = &ssh_gss_libraries[j]; + for (j = 0; j < ssh->gsslibs->nlibraries; j++) + if (ssh->gsslibs->libraries[j].id == want_id) { + s->gsslib = &ssh->gsslibs->libraries[j]; goto got_gsslib; /* double break */ } } @@ -8549,11 +8625,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, sfree(s->password); } else { + char *str = dupprintf("No supported authentication methods available" + " (server sent: %.*s)", + methlen, methods); - ssh_disconnect(ssh, NULL, + ssh_disconnect(ssh, str, "No supported authentication methods available", SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, FALSE); + sfree(str); + crStopV; } @@ -8570,6 +8651,20 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, if (s->agent_response) sfree(s->agent_response); + if (s->userauth_success) { + /* + * We've just received USERAUTH_SUCCESS, and we haven't sent any + * packets since. Signal the transport layer to consider enacting + * delayed compression. + * + * (Relying on we_are_in is not sufficient, as + * draft-miller-secsh-compression-delayed is quite clear that it + * triggers on USERAUTH_SUCCESS specifically, and we_are_in can + * become set for other reasons.) + */ + do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); + } + /* * Now the connection protocol has started, one way or another. */ @@ -9274,6 +9369,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data); ssh->kex_in_progress = FALSE; +#ifndef NO_GSSAPI + ssh->gsslibs = NULL; +#endif + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) return p; @@ -9334,6 +9433,7 @@ static void ssh_free(void *handle) x11_close(c->u.x11.s); break; case CHAN_SOCKDATA: + case CHAN_SOCKDATA_DORMANT: if (c->u.pfd.s != NULL) pfd_close(c->u.pfd.s); break; @@ -9346,7 +9446,7 @@ static void ssh_free(void *handle) if (ssh->rportfwds) { while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) - sfree(pf); + free_rportfwd(pf); freetree234(ssh->rportfwds); ssh->rportfwds = NULL; } @@ -9370,6 +9470,10 @@ static void ssh_free(void *handle) if (ssh->pinger) pinger_free(ssh->pinger); bufchain_clear(&ssh->queued_incoming_data); +#ifndef NO_GSSAPI + if (ssh->gsslibs) + ssh_gss_cleanup(ssh->gsslibs); +#endif sfree(ssh); random_unref();