X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/3fe9213279e9971f98dbb78146ce78ad59858e66..e693a2d9c8385b9177a24e28ab63701973975919:/ssh.c diff --git a/ssh.c b/ssh.c index 0815ff32..f4b61542 100644 --- a/ssh.c +++ b/ssh.c @@ -106,6 +106,7 @@ */ #define SSH2_PKTCTX_DHGROUP 0x0001 #define SSH2_PKTCTX_DHGEX 0x0002 +#define SSH2_PKTCTX_KEX_MASK 0x000F #define SSH2_PKTCTX_PUBLICKEY 0x0010 #define SSH2_PKTCTX_PASSWORD 0x0020 #define SSH2_PKTCTX_KBDINTER 0x0040 @@ -162,7 +163,7 @@ static const char *const ssh2_disconnect_reasons[] = { #define BUG_CHOKES_ON_RSA 8 #define BUG_SSH2_RSA_PADDING 16 #define BUG_SSH2_DERIVEKEY 32 -/* 64 was BUG_SSH2_DH_GEX, now spare */ +#define BUG_SSH2_REKEY 64 #define BUG_SSH2_PK_SESSIONID 128 #define translate(x) if (type == x) return #x @@ -750,6 +751,7 @@ struct ssh_tag { unsigned long max_data_size; int kex_in_progress; long next_rekey, last_rekey; + char *deferred_rekey_reason; /* points to STATIC string; don't free */ }; #define logevent(s) logevent(ssh->frontend, s) @@ -1737,8 +1739,7 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) if (!ssh->kex_in_progress && ssh->max_data_size != 0 && ssh->outgoing_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "Initiating key re-exchange " - "(too much data sent)", -1, NULL); + do_ssh2_transport(ssh, "too much data sent", -1, NULL); ssh_free_packet(pkt); } @@ -1828,8 +1829,7 @@ static void ssh_pkt_defersend(Ssh ssh) if (!ssh->kex_in_progress && ssh->max_data_size != 0 && ssh->outgoing_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "Initiating key re-exchange " - "(too much data sent)", -1, NULL); + do_ssh2_transport(ssh, "too much data sent", -1, NULL); ssh->deferred_data_size = 0; } @@ -2136,6 +2136,16 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID; logevent("We believe remote version has SSH2 public-key-session-ID bug"); } + + if (ssh->cfg.sshbug_rekey2 == FORCE_ON || + (ssh->cfg.sshbug_rekey2 == AUTO && + wc_match("Sun_SSH_1.0", imp))) { + /* + * These versions have the SSH2 ignore-rekey bug. + */ + ssh->remote_bugs |= BUG_SSH2_REKEY; + logevent("We believe remote version has SSH2 ignore-rekey bug"); + } } /* @@ -2833,8 +2843,11 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } /* Warn about chosen cipher if necessary. */ - if (warn) + if (warn) { + sk_set_frozen(ssh->s, 1); askalg(ssh->frontend, "cipher", cipher_string); + sk_set_frozen(ssh->s, 0); + } } switch (s->cipher_type) { @@ -4649,7 +4662,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, int n_preferred_ciphers; const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; const struct ssh_compress *preferred_comp; - int first_kex; + int got_session_id, activated_authconn; struct Packet *pktout; }; crState(do_ssh2_transport_state); @@ -4660,10 +4673,21 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, s->csmac_tobe = s->scmac_tobe = NULL; s->cscomp_tobe = s->sccomp_tobe = NULL; - s->first_kex = 1; + s->got_session_id = s->activated_authconn = FALSE; + + /* + * Be prepared to work around the buggy MAC problem. + */ + if (ssh->remote_bugs & BUG_SSH2_HMAC) + s->maclist = buggymacs, s->nmacs = lenof(buggymacs); + else + s->maclist = macs, s->nmacs = lenof(macs); + begin_key_exchange: + ssh->pkt_ctx &= ~SSH2_PKTCTX_KEX_MASK; { - int i; + int i, j, commalist_started; + /* * Set up the preferred key exchange. (NULL => warn below here) */ @@ -4691,10 +4715,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, break; } } - } - { - int i; /* * Set up the preferred ciphers. (NULL => warn below here) */ @@ -4724,27 +4745,14 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, break; } } - } - - /* - * Set up preferred compression. - */ - if (ssh->cfg.compression) - s->preferred_comp = &ssh_zlib; - else - s->preferred_comp = &ssh_comp_none; - - /* - * Be prepared to work around the buggy MAC problem. - */ - if (ssh->remote_bugs & BUG_SSH2_HMAC) - s->maclist = buggymacs, s->nmacs = lenof(buggymacs); - else - s->maclist = macs, s->nmacs = lenof(macs); - begin_key_exchange: - { - int i, j, commalist_started; + /* + * Set up preferred compression. + */ + if (ssh->cfg.compression) + s->preferred_comp = &ssh_zlib; + else + s->preferred_comp = &ssh_comp_none; /* * Enable queueing of outgoing auth- or connection-layer @@ -4894,9 +4902,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, ssh->kex = k; } if (ssh->kex) { - if (s->warn) + if (s->warn) { + sk_set_frozen(ssh->s, 1); askalg(ssh->frontend, "key-exchange algorithm", ssh->kex->name); + sk_set_frozen(ssh->s, 0); + } break; } } @@ -4927,9 +4938,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, } } if (s->cscipher_tobe) { - if (s->warn) + if (s->warn) { + sk_set_frozen(ssh->s, 1); askalg(ssh->frontend, "client-to-server cipher", s->cscipher_tobe->name); + sk_set_frozen(ssh->s, 0); + } break; } } @@ -4954,9 +4968,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, } } if (s->sccipher_tobe) { - if (s->warn) + if (s->warn) { + sk_set_frozen(ssh->s, 1); askalg(ssh->frontend, "server-to-client cipher", s->sccipher_tobe->name); + sk_set_frozen(ssh->s, 0); + } break; } } @@ -5113,10 +5130,12 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, */ s->keystr = ssh->hostkey->fmtkey(s->hkey); s->fingerprint = ssh->hostkey->fingerprint(s->hkey); + sk_set_frozen(ssh->s, 1); verify_ssh_host_key(ssh->frontend, ssh->savedhost, ssh->savedport, ssh->hostkey->keytype, s->keystr, s->fingerprint); - if (s->first_kex) { /* don't bother logging this in rekeys */ + sk_set_frozen(ssh->s, 0); + if (!s->got_session_id) { /* don't bother logging this in rekeys */ logevent("Host key fingerprint is:"); logevent(s->fingerprint); } @@ -5129,9 +5148,11 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * the session id, used in session key construction and * authentication. */ - if (s->first_kex) + if (!s->got_session_id) { memcpy(ssh->v2_session_id, s->exchange_hash, sizeof(s->exchange_hash)); + s->got_session_id = TRUE; + } /* * Send SSH2_MSG_NEWKEYS. @@ -5249,14 +5270,25 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, } /* - * Key exchange is over. Schedule a timer for our next rekey. + * Key exchange is over. Loop straight back round if we have a + * deferred rekey reason. + */ + if (ssh->deferred_rekey_reason) { + logevent(ssh->deferred_rekey_reason); + pktin = NULL; + ssh->deferred_rekey_reason = NULL; + goto begin_key_exchange; + } + + /* + * Otherwise, schedule a timer for our next rekey. */ ssh->kex_in_progress = FALSE; ssh->last_rekey = GETTICKCOUNT(); if (ssh->cfg.ssh_rekey_time != 0) ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, ssh2_timer, ssh); - + /* * If this is the first key exchange phase, we must pass the * SSH2_MSG_NEWKEYS packet to the next layer, not because it @@ -5265,10 +5297,10 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because * it would only confuse the layer above. */ - if (!s->first_kex) { + if (s->activated_authconn) { crReturn(1); } - s->first_kex = 0; + s->activated_authconn = TRUE; /* * Now we're encrypting. Begin returning 1 to the protocol main @@ -5283,12 +5315,35 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, */ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || (!pktin && inlen == -1))) { + wait_for_rekey: crReturn(1); } if (pktin) { logevent("Server initiated key re-exchange"); } else { - logevent((char *)in); + /* + * 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, + * we process it anyway!) + */ + if ((ssh->remote_bugs & BUG_SSH2_REKEY)) { + logeventf(ssh, "Server bug prevents key re-exchange (%s)", + (char *)in); + /* Reset the counters, so that at least this message doesn't + * hit the event log _too_ often. */ + ssh->outgoing_data_size = 0; + ssh->incoming_data_size = 0; + if (ssh->cfg.ssh_rekey_time != 0) { + ssh->next_rekey = + schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC, + ssh2_timer, ssh); + } + goto wait_for_rekey; /* this is utterly horrid */ + } else { + logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); + logevent((char *)in); + } } goto begin_key_exchange; @@ -5913,8 +5968,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET } type; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; - int tried_pubkey_config, tried_agent, tried_keyb_inter; - int kbd_inter_running; + int tried_pubkey_config, tried_agent; + int kbd_inter_running, kbd_inter_refused; int we_are_in; int num_prompts, curr_prompt, echo; char username[100]; @@ -6043,8 +6098,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->tried_pubkey_config = FALSE; s->tried_agent = FALSE; - s->tried_keyb_inter = FALSE; s->kbd_inter_running = FALSE; + s->kbd_inter_refused = FALSE; /* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */ if (!filename_is_null(ssh->cfg.keyfile)) { int keytype; @@ -6111,6 +6166,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ if (!s->gotit) s->curr_prompt = 0; + } else if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + /* FIXME: perhaps we should support this? */ + bombout(("PASSWD_CHANGEREQ not yet supported")); + crStopV; } else if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { bombout(("Strange packet received during authentication: type %d", pktin->type)); @@ -6435,10 +6494,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } } - if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) { + if (!s->method && s->can_keyb_inter && !s->kbd_inter_refused && + !s->kbd_inter_running) { s->method = AUTH_KEYBOARD_INTERACTIVE; s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - s->tried_keyb_inter = TRUE; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; @@ -6457,6 +6516,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->gotit = TRUE; logevent("Keyboard-interactive authentication refused"); s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ continue; } @@ -6467,7 +6527,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, if (s->kbd_inter_running) { s->method = AUTH_KEYBOARD_INTERACTIVE; s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - s->tried_keyb_inter = TRUE; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; @@ -7258,8 +7317,7 @@ static void ssh2_timer(void *ctx, long now) if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 && now - ssh->next_rekey >= 0) { - do_ssh2_transport(ssh, "Initiating key re-exchange (timeout)", - -1, NULL); + do_ssh2_transport(ssh, "timeout", -1, NULL); } } @@ -7274,8 +7332,7 @@ static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, if (!ssh->kex_in_progress && ssh->max_data_size != 0 && ssh->incoming_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "Initiating key re-exchange " - "(too much data received)", -1, NULL); + do_ssh2_transport(ssh, "too much data received", -1, NULL); } if (pktin && ssh->packet_dispatch[pktin->type]) { @@ -7366,6 +7423,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->queuelen = ssh->queuesize = 0; ssh->queueing = FALSE; ssh->qhead = ssh->qtail = NULL; + ssh->deferred_rekey_reason = NULL; *backend_handle = ssh; @@ -7503,7 +7561,7 @@ static void ssh_free(void *handle) static void ssh_reconfig(void *handle, Config *cfg) { Ssh ssh = (Ssh) handle; - char *rekeying = NULL; + char *rekeying = NULL, rekey_mandatory = FALSE; unsigned long old_max_data_size; pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); @@ -7515,7 +7573,7 @@ static void ssh_reconfig(void *handle, Config *cfg) long now = GETTICKCOUNT(); if (new_next - now < 0) { - rekeying = "Initiating key re-exchange (timeout shortened)"; + rekeying = "timeout shortened"; } else { ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); } @@ -7527,14 +7585,30 @@ static void ssh_reconfig(void *handle, Config *cfg) ssh->max_data_size != 0) { if (ssh->outgoing_data_size > ssh->max_data_size || ssh->incoming_data_size > ssh->max_data_size) - rekeying = "Initiating key re-exchange (data limit lowered)"; + rekeying = "data limit lowered"; + } + + if (ssh->cfg.compression != cfg->compression) { + rekeying = "compression setting changed"; + rekey_mandatory = TRUE; } - if (rekeying && !ssh->kex_in_progress) { - do_ssh2_transport(ssh, rekeying, -1, NULL); + if (ssh->cfg.ssh2_des_cbc != cfg->ssh2_des_cbc || + memcmp(ssh->cfg.ssh_cipherlist, cfg->ssh_cipherlist, + sizeof(ssh->cfg.ssh_cipherlist))) { + rekeying = "cipher settings changed"; + rekey_mandatory = TRUE; } ssh->cfg = *cfg; /* STRUCTURE COPY */ + + if (rekeying) { + if (!ssh->kex_in_progress) { + do_ssh2_transport(ssh, rekeying, -1, NULL); + } else if (rekey_mandatory) { + ssh->deferred_rekey_reason = rekeying; + } + } } /* @@ -7735,8 +7809,7 @@ static void ssh_special(void *handle, Telnet_Special code) } } else if (code == TS_REKEY) { if (!ssh->kex_in_progress && ssh->version == 2) { - do_ssh2_transport(ssh, "Initiating key re-exchange at" - " user request", -1, NULL); + do_ssh2_transport(ssh, "at user request", -1, NULL); } } else if (code == TS_BRK) { if (ssh->state == SSH_STATE_CLOSED