X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/c0a8159289da8df577cc28be1f0c9f7f5c8d33c1..2ec078faddb753febe340e1ff2b4b2b8f0aaa5e8:/ssh.c diff --git a/ssh.c b/ssh.c index 1885088f..3e8396fd 100644 --- a/ssh.c +++ b/ssh.c @@ -182,6 +182,8 @@ static const char *const ssh2_disconnect_reasons[] = { #define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 #define BUG_CHOKES_ON_RSA 8 #define BUG_SSH2_RSA_PADDING 16 +#define BUG_SSH2_DERIVEKEY 32 +#define BUG_SSH2_DH_GEX 64 static int ssh_pkt_ctx = 0; @@ -425,6 +427,16 @@ enum { /* channel types */ struct ssh_channel { unsigned remoteid, localid; int type; + /* + * In SSH1, this value contains four bits: + * + * 1 We have sent SSH1_MSG_CHANNEL_CLOSE. + * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * 4 We have received SSH1_MSG_CHANNEL_CLOSE. + * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. + * + * A channel is completely finished with when all four bits are set. + */ int closes; union { struct ssh1_data_channel { @@ -803,11 +815,11 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) if (pktin.type == SSH1_MSG_DEBUG) { /* log debug message */ - char buf[80]; + char buf[512]; int stringlen = GET_32BIT(pktin.body); - strcpy(buf, "Remote: "); - if (stringlen > 70) - stringlen = 70; + strcpy(buf, "Remote debug message: "); + if (stringlen > 480) + stringlen = 480; memcpy(buf + 8, pktin.body + 4, stringlen); buf[8 + stringlen] = '\0'; logevent(buf); @@ -885,6 +897,15 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) st->pad = pktin.data[4]; /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) { + bombout(("Incoming packet was garbled on decryption")); + crReturn(0); + } + + /* * This enables us to deduce the payload length. */ st->payload = st->len - st->pad - 1; @@ -1650,9 +1671,11 @@ static void ssh_detect_bugs(char *vstring) ssh_remote_bugs = 0; - if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || - !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || - !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) { + if (cfg.sshbug_ignore1 == BUG_ON || + (cfg.sshbug_ignore1 == BUG_AUTO && + (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || + !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || + !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")))) { /* * These versions don't support SSH1_MSG_IGNORE, so we have * to use a different defence against password length @@ -1662,7 +1685,9 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version has SSH1 ignore bug"); } - if (!strcmp(imp, "Cisco-1.25")) { + if (cfg.sshbug_plainpw1 == BUG_ON || + (cfg.sshbug_plainpw1 == BUG_AUTO && + (!strcmp(imp, "Cisco-1.25")))) { /* * These versions need a plain password sent; they can't * handle having a null and a random length of data after @@ -1672,7 +1697,9 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version needs a plain SSH1 password"); } - if (!strcmp(imp, "Cisco-1.25")) { + if (cfg.sshbug_rsa1 == BUG_ON || + (cfg.sshbug_rsa1 == BUG_AUTO && + (!strcmp(imp, "Cisco-1.25")))) { /* * These versions apparently have no clue whatever about * RSA authentication and will panic and die if they see @@ -1682,9 +1709,11 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version can't handle RSA authentication"); } - if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) || - !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) || - !strncmp(imp, "2.1 ", 4)) { + if (cfg.sshbug_hmac2 == BUG_ON || + (cfg.sshbug_hmac2 == BUG_AUTO && + (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) || + !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) || + !strncmp(imp, "2.1 ", 4)))) { /* * These versions have the HMAC bug. */ @@ -1692,14 +1721,36 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version has SSH2 HMAC bug"); } - if ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') || - (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')) { + if (cfg.sshbug_derivekey2 == BUG_ON || + (cfg.sshbug_derivekey2 == BUG_AUTO && + (!strncmp(imp, "2.0.", 4)))) { + /* + * These versions have the key-derivation bug (failing to + * include the literal shared secret in the hashes that + * generate the keys). + */ + ssh_remote_bugs |= BUG_SSH2_DERIVEKEY; + logevent("We believe remote version has SSH2 key-derivation bug"); + } + + if (cfg.sshbug_rsapad2 == BUG_ON || + (cfg.sshbug_rsapad2 == BUG_AUTO && + ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') || + (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')))){ /* * These versions have the SSH2 RSA padding bug. */ ssh_remote_bugs |= BUG_SSH2_RSA_PADDING; logevent("We believe remote version has SSH2 RSA padding bug"); } + + if (cfg.sshbug_dhgex2 == BUG_ON) { + /* + * These versions have the SSH2 DH GEX bug. + */ + ssh_remote_bugs |= BUG_SSH2_DH_GEX; + logevent("We believe remote version has SSH2 DH group exchange bug"); + } } static int do_ssh_init(unsigned char c) @@ -1710,6 +1761,7 @@ static int do_ssh_init(unsigned char c) static int vstrsize; static char *vlog; static int i; + static int proto1, proto2; crBegin; @@ -1766,12 +1818,26 @@ static int do_ssh_init(unsigned char c) sfree(vlog); /* - * Server version "1.99" means we can choose whether we use v1 - * or v2 protocol. Choice is based on cfg.sshprot. + * Decide which SSH protocol version to support. */ - if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) { + + /* Anything strictly below "2.0" means protocol 1 is supported. */ + proto1 = ssh_versioncmp(version, "2.0") < 0; + /* Anything greater or equal to "1.99" means protocol 2 is supported. */ + proto2 = ssh_versioncmp(version, "1.99") >= 0; + + if (cfg.sshprot == 0 && !proto1) { + bombout(("SSH protocol version 1 required by user but not provided by server")); + crReturn(0); + } + if (cfg.sshprot == 3 && !proto2) { + bombout(("SSH protocol version 2 required by user but not provided by server")); + crReturn(0); + } + + if (proto2 && (cfg.sshprot >= 2 || !proto1)) { /* - * This is a v2 server. Begin v2 protocol. + * Use v2 protocol. */ char verstring[80], vlog[100]; sprintf(verstring, "SSH-2.0-%s", sshver); @@ -1791,7 +1857,7 @@ static int do_ssh_init(unsigned char c) s_rdpkt = ssh2_rdpkt; } else { /* - * This is a v1 server. Begin v1 protocol. + * Use v1 protocol. */ char verstring[80], vlog[100]; sprintf(verstring, "SSH-%s-%s", @@ -1800,11 +1866,6 @@ static int do_ssh_init(unsigned char c) sprintf(vlog, "We claim version: %s", verstring); logevent(vlog); strcat(verstring, "\n"); - - if (cfg.sshprot == 3) { - bombout(("SSH protocol version 2 required by user but not provided by server")); - crReturn(0); - } logevent("Using SSH protocol version 1"); sk_write(s, verstring, strlen(verstring)); @@ -2039,6 +2100,76 @@ static void ssh_throttle_all(int enable, int bufsize) } /* + * Username and password input, abstracted off into reusable + * routines (hopefully even reusable between SSH1 and SSH2!). + */ +static char *ssh_userpass_input_buffer; +static int ssh_userpass_input_buflen; +static int ssh_userpass_input_bufpos; +static int ssh_userpass_input_echo; + +/* Set up a username or password input loop on a given buffer. */ +void setup_userpass_input(char *buffer, int buflen, int echo) +{ + ssh_userpass_input_buffer = buffer; + ssh_userpass_input_buflen = buflen; + ssh_userpass_input_bufpos = 0; + ssh_userpass_input_echo = echo; +} + +/* + * Process some terminal data in the course of username/password + * input. Returns >0 for success (line of input returned in + * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0 + * for inconclusive (keep waiting for more input please). + */ +int process_userpass_input(unsigned char *in, int inlen) +{ + char c; + + while (inlen--) { + switch (c = *in++) { + case 10: + case 13: + ssh_userpass_input_buffer[ssh_userpass_input_bufpos] = 0; + ssh_userpass_input_buffer[ssh_userpass_input_buflen-1] = 0; + return +1; + break; + case 8: + case 127: + if (ssh_userpass_input_bufpos > 0) { + if (ssh_userpass_input_echo) + c_write_str("\b \b"); + ssh_userpass_input_bufpos--; + } + break; + case 21: + case 27: + while (ssh_userpass_input_bufpos > 0) { + if (ssh_userpass_input_echo) + c_write_str("\b \b"); + ssh_userpass_input_bufpos--; + } + break; + case 3: + case 4: + return -1; + break; + default: + if (((c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) + && ssh_userpass_input_bufpos < ssh_userpass_input_buflen-1) { + ssh_userpass_input_buffer[ssh_userpass_input_bufpos++] = c; + if (ssh_userpass_input_echo) + c_write(&c, 1); + } + break; + } + } + return 0; +} + +/* * Handle the key exchange and user authentication phases. */ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) @@ -2223,8 +2354,6 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) fflush(stdout); { - static int pos = 0; - static char c; if ((flags & FLAG_INTERACTIVE) && !*cfg.username) { if (ssh_get_line && !ssh_getline_pw_only) { if (!ssh_get_line("login as: ", @@ -2238,51 +2367,22 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crReturn(1); } } else { + static int ret; c_write_str("login as: "); ssh_send_ok = 1; - while (pos >= 0) { + + setup_userpass_input(username, sizeof(username), 1); + do { crWaitUntil(!ispkt); - while (inlen--) - switch (c = *in++) { - case 10: - case 13: - username[pos] = 0; - pos = -1; - break; - case 8: - case 127: - if (pos > 0) { - c_write_str("\b \b"); - pos--; - } - break; - case 21: - case 27: - while (pos > 0) { - c_write_str("\b \b"); - pos--; - } - break; - case 3: - case 4: - cleanup_exit(0); - break; - default: - if (((c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) - && pos < sizeof(username)-1) { - username[pos++] = c; - c_write(&c, 1); - } - break; - } - } + ret = process_userpass_input(in, inlen); + } while (ret == 0); + if (ret < 0) + cleanup_exit(0); c_write_str("\r\n"); - username[strcspn(username, "\n\r")] = '\0'; } } else { - strncpy(username, cfg.username, 99); - username[99] = '\0'; + strncpy(username, cfg.username, sizeof(username)); + username[sizeof(username)-1] = '\0'; } send_packet(SSH1_CMSG_USER, PKT_STR, username, PKT_END); @@ -2562,37 +2662,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) } else { /* Prompt may have come from server. We've munged it a bit, so * we know it to be zero-terminated at least once. */ + static int ret; c_write_untrusted(prompt, strlen(prompt)); pos = 0; - ssh_send_ok = 1; - while (pos >= 0) { + + setup_userpass_input(password, sizeof(password), 0); + do { crWaitUntil(!ispkt); - while (inlen--) - switch (c = *in++) { - case 10: - case 13: - password[pos] = 0; - pos = -1; - break; - case 8: - case 127: - if (pos > 0) - pos--; - break; - case 21: - case 27: - pos = 0; - break; - case 3: - case 4: - cleanup_exit(0); - break; - default: - if (pos < sizeof(password)-1) - password[pos++] = c; - break; - } - } + ret = process_userpass_input(in, inlen); + } while (ret == 0); + if (ret < 0) + cleanup_exit(0); c_write_str("\r\n"); } @@ -2827,7 +2907,7 @@ void sshfwd_close(struct ssh_channel *c) ssh2_pkt_send(); } } - c->closes = 1; + c->closes = 1; /* sent MSG_CLOSE */ if (c->type == CHAN_X11) { c->u.x11.s = NULL; logevent("Forwarded X11 connection terminated"); @@ -3283,13 +3363,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) unsigned i = GET_32BIT(pktin.body); struct ssh_channel *c; c = find234(ssh_channels, &i, ssh_channelfind); - if (c) { + if (c && ((int)c->remoteid) != -1) { int closetype; closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); - if (!(c->closes & closetype)) - send_packet(pktin.type, PKT_INT, c->remoteid, - PKT_END); + if ((c->closes == 0) && (c->type == CHAN_X11)) { logevent("Forwarded X11 connection terminated"); assert(c->u.x11.s != NULL); @@ -3302,11 +3380,23 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) pfd_close(c->u.pfd.s); c->u.pfd.s = NULL; } - c->closes |= closetype; - if (c->closes == 3) { + + c->closes |= (closetype << 2); /* seen this message */ + if (!(c->closes & closetype)) { + send_packet(pktin.type, PKT_INT, c->remoteid, + PKT_END); + c->closes |= closetype; /* sent it too */ + } + + if (c->closes == 15) { del234(ssh_channels, c); sfree(c); } + } else { + bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n", + pktin.type == SSH1_MSG_CHANNEL_CLOSE ? "" : + "_CONFIRMATION", c ? "half-open" : "nonexistent", + i)); } } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) { /* Data sent down one of our channels. */ @@ -3459,14 +3549,16 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, SHA_State s; /* First 20 bytes. */ SHA_Init(&s); - sha_mpint(&s, K); + if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY)) + sha_mpint(&s, K); SHA_Bytes(&s, H, 20); SHA_Bytes(&s, &chr, 1); SHA_Bytes(&s, sessid, 20); SHA_Final(&s, keyspace); /* Next 20 bytes. */ SHA_Init(&s); - sha_mpint(&s, K); + if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY)) + sha_mpint(&s, K); SHA_Bytes(&s, H, 20); SHA_Bytes(&s, keyspace, 20); SHA_Final(&s, keyspace + 20); @@ -3550,7 +3642,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) /* * Be prepared to work around the buggy MAC problem. */ - if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC)) + if (ssh_remote_bugs & BUG_SSH2_HMAC) maclist = buggymacs, nmacs = lenof(buggymacs); else maclist = macs, nmacs = lenof(macs); @@ -3565,6 +3657,9 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) /* List key exchange algorithms. */ ssh2_pkt_addstring_start(); for (i = 0; i < lenof(kex_algs); i++) { + if (kex_algs[i] == &ssh_diffiehellman_gex && + (ssh_remote_bugs & BUG_SSH2_DH_GEX)) + continue; ssh2_pkt_addstring_str(kex_algs[i]->name); if (i < lenof(kex_algs) - 1) ssh2_pkt_addstring_str(","); @@ -3671,6 +3766,9 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) pktin.savedpos += 16; /* skip garbage cookie */ ssh2_pkt_getstring(&str, &len); /* key exchange algorithms */ for (i = 0; i < lenof(kex_algs); i++) { + if (kex_algs[i] == &ssh_diffiehellman_gex && + (ssh_remote_bugs & BUG_SSH2_DH_GEX)) + continue; if (in_commasep_string(kex_algs[i]->name, str, len)) { kex = kex_algs[i]; break; @@ -4082,13 +4180,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) username[0] = '\0'; got_username = FALSE; do { - static int pos; - static char c; - /* * Get a username. */ - pos = 0; if (got_username && !cfg.change_username) { /* * We got a username last time round this loop, and @@ -4108,52 +4202,23 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) crReturnV; } } else { + static int ret; c_write_str("login as: "); ssh_send_ok = 1; - while (pos >= 0) { + setup_userpass_input(username, sizeof(username), 1); + do { crWaitUntilV(!ispkt); - while (inlen--) - switch (c = *in++) { - case 10: - case 13: - username[pos] = 0; - pos = -1; - break; - case 8: - case 127: - if (pos > 0) { - c_write_str("\b \b"); - pos--; - } - break; - case 21: - case 27: - while (pos > 0) { - c_write_str("\b \b"); - pos--; - } - break; - case 3: - case 4: - cleanup_exit(0); - break; - default: - if (((c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) - && pos < sizeof(username)-1) { - username[pos++] = c; - c_write(&c, 1); - } - break; - } - } + ret = process_userpass_input(in, inlen); + } while (ret == 0); + if (ret < 0) + cleanup_exit(0); } c_write_str("\r\n"); username[strcspn(username, "\n\r")] = '\0'; } else { char stuff[200]; - strncpy(username, cfg.username, 99); - username[99] = '\0'; + strncpy(username, cfg.username, sizeof(username)); + username[sizeof(username)-1] = '\0'; if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { sprintf(stuff, "Using username \"%s\".\r\n", username); c_write_str(stuff); @@ -4315,6 +4380,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) method = 0; ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + /* + * Most password/passphrase prompts will be + * non-echoing, so we set this to 0 by default. + * Exception is that some keyboard-interactive prompts + * can be echoing, in which case we'll set this to 1. + */ + echo = 0; + if (!method && can_pubkey && agent_exists() && !tried_agent) { /* * Attempt public-key authentication using Pageant. @@ -4569,11 +4642,11 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_getstring(&lang, &lang_len); if (name_len > 0) { c_write_untrusted(name, name_len); - c_write_str("\n"); + c_write_str("\r\n"); } if (inst_len > 0) { c_write_untrusted(inst, inst_len); - c_write_str("\n"); + c_write_str("\r\n"); } num_prompts = ssh2_pkt_getuint32(); } @@ -4632,41 +4705,17 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) crReturnV; } } else { - static int pos = 0; - static char c; - + static int ret; c_write_untrusted(pwprompt, strlen(pwprompt)); ssh_send_ok = 1; - pos = 0; - while (pos >= 0) { + setup_userpass_input(password, sizeof(password), echo); + do { crWaitUntilV(!ispkt); - while (inlen--) - switch (c = *in++) { - case 10: - case 13: - password[pos] = 0; - pos = -1; - break; - case 8: - case 127: - if (pos > 0) - pos--; - break; - case 21: - case 27: - pos = 0; - break; - case 3: - case 4: - cleanup_exit(0); - break; - default: - if (pos < sizeof(password)-1) - password[pos++] = c; - break; - } - } + ret = process_userpass_input(in, inlen); + } while (ret == 0); + if (ret < 0) + cleanup_exit(0); c_write_str("\r\n"); } } @@ -5330,8 +5379,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) struct ssh_channel *c; c = find234(ssh_channels, &i, ssh_channelfind); - if (!c) - continue; /* nonexistent channel */ + if (!c || ((int)c->remoteid) == -1) { + bombout(("Received CHANNEL_CLOSE for %s channel %d\n", + c ? "half-open" : "nonexistent", i)); + } /* Do pre-close processing on the channel. */ switch (c->type) { case CHAN_MAINSESSION: @@ -5785,7 +5836,8 @@ static void ssh_special(Telnet_Special code) if (ssh_state == SSH_STATE_CLOSED || ssh_state == SSH_STATE_PREPACKET) return; if (ssh_version == 1) { - send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); + if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); } else { ssh2_pkt_init(SSH2_MSG_IGNORE); ssh2_pkt_addstring_start();