X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/7963af2f0ee079aba37c6e1c9cea120ebeba83a0..7781f316d0d6aa3e286c588fb0efc832037e6866:/ssh.c diff --git a/ssh.c b/ssh.c index 9ef7e0c6..b270ac53 100644 --- a/ssh.c +++ b/ssh.c @@ -159,6 +159,8 @@ static const char *const ssh2_disconnect_reasons[] = { */ #define BUG_CHOKES_ON_SSH1_IGNORE 1 #define BUG_SSH2_HMAC 2 +#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 + #define GET_32BIT(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 24) | \ @@ -607,12 +609,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) (*data)++, (*datalen)--; } -#ifdef FWHACK - if (st->len == 0x52656d6f) { /* "Remo"te server has closed ... */ - st->len = 0x300; /* big enough to carry to end */ - } -#endif - st->pad = 8 - (st->len % 8); st->biglen = st->len + st->pad; pktin.length = st->len - 5; @@ -726,6 +722,7 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) buf[nowlen + msglen] = '\0'; logevent(buf); bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen)); + crReturn(0); } crFinish(0); @@ -769,11 +766,7 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) pktin.data[st->i] = *(*data)++; (*datalen)--; } -#ifdef FWHACK - if (!memcmp(pktin.data, "Remo", 4)) { /* "Remo"te server has closed ... */ - /* FIXME */ - } -#endif + if (sccipher) sccipher->decrypt(pktin.data, st->cipherblk); @@ -904,6 +897,7 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? ssh2_disconnect_reasons[reason] : "unknown", buf+nowlen)); + crReturn(0); } crFinish(0); @@ -948,23 +942,18 @@ static int s_wrpkt_prepare(void) pktout.body[-1] = pktout.type; -#ifdef DUMP_PACKETS - debug(("Packet payload pre-compression:\n")); - dmemdump(pktout.body - 1, pktout.length + 1); -#endif - if (ssh1_compressing) { unsigned char *compblk; int complen; +#ifdef DUMP_PACKETS + debug(("Packet payload pre-compression:\n")); + dmemdump(pktout.body - 1, pktout.length + 1); +#endif zlib_compress_block(pktout.body - 1, pktout.length + 1, &compblk, &complen); ssh1_pktout_size(complen - 1); memcpy(pktout.body - 1, compblk, complen); sfree(compblk); -#ifdef DUMP_PACKETS - debug(("Packet payload post-compression:\n")); - dmemdump(pktout.body - 1, pktout.length + 1); -#endif } len = pktout.length + 5; /* type and CRC */ @@ -1247,13 +1236,15 @@ static int ssh2_pkt_construct(void) /* * Compress packet payload. */ -#ifdef DUMP_PACKETS - debug(("Pre-compression payload:\n")); - dmemdump(pktout.data + 5, pktout.length - 5); -#endif { unsigned char *newpayload; int newlen; +#ifdef DUMP_PACKETS + if (cscomp && cscomp != &ssh_comp_none) { + debug(("Pre-compression payload:\n")); + dmemdump(pktout.data + 5, pktout.length - 5); + } +#endif if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5, &newpayload, &newlen)) { pktout.length = 5; @@ -1426,17 +1417,15 @@ static void ssh_detect_bugs(char *vstring) char *imp; /* pointer to implementation part */ imp = vstring; imp += strcspn(imp, "-"); - if (*imp) - imp++; + if (*imp) imp++; imp += strcspn(imp, "-"); - if (*imp) - imp++; + if (*imp) imp++; 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, "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 @@ -1446,6 +1435,16 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version has SSH1 ignore bug"); } + if (!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 + * the password. + */ + ssh_remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; + logevent("We believe remote version needs a plain SSH1 password"); + } + 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)) { @@ -1487,8 +1486,8 @@ static int do_ssh_init(unsigned char c) crReturn(1); /* get another character */ } - vstring = smalloc(16); vstrsize = 16; + vstring = smalloc(vstrsize); strcpy(vstring, "SSH-"); vslen = 4; i = 0; @@ -1514,10 +1513,10 @@ static int do_ssh_init(unsigned char c) vstring[vslen] = 0; vlog = smalloc(20 + vslen); + vstring[strcspn (vstring, "\r\n")] = '\0'; /* remove end-of-line chars */ sprintf(vlog, "Server version: %s", vstring); - ssh_detect_bugs(vstring); - vlog[strcspn(vlog, "\r\n")] = '\0'; logevent(vlog); + ssh_detect_bugs(vstring); sfree(vlog); /* @@ -1600,6 +1599,9 @@ static void ssh_gotdata(unsigned char *data, int datalen) while (1) { while (datalen > 0) { if (s_rdpkt(&data, &datalen) == 0) { + if (ssh_state == SSH_STATE_CLOSED) { + return; + } ssh_protocol(NULL, 0, 1); if (ssh_state == SSH_STATE_CLOSED) { return; @@ -1794,6 +1796,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) struct MD5Context md5c; static unsigned long supported_ciphers_mask, supported_auths_mask; static int tried_publickey; + static int tis_auth_refused, ccard_auth_refused; static unsigned char session_id[16]; static int cipher_type; static char username[100]; @@ -2042,6 +2045,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crWaitUntil(ispkt); tried_publickey = 0; + tis_auth_refused = ccard_auth_refused = 0; while (pktin.type == SSH1_SMSG_FAILURE) { static char password[100]; @@ -2049,12 +2053,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static int pos; static char c; static int pwpkt_type; - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + if (agent_exists()) { /* * Attempt RSA authentication using Pageant. @@ -2177,9 +2177,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) if (*cfg.keyfile && !tried_publickey) pwpkt_type = SSH1_CMSG_AUTH_RSA; - if (pktin.type == SSH1_SMSG_FAILURE && - cfg.try_tis_auth && - (supported_auths_mask & (1 << SSH1_AUTH_TIS))) { + if (cfg.try_tis_auth && + (supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !tis_auth_refused) { pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; logevent("Requested TIS authentication"); send_packet(SSH1_CMSG_AUTH_TIS, PKT_END); @@ -2188,6 +2188,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) logevent("TIS authentication declined"); if (flags & FLAG_INTERACTIVE) c_write_str("TIS authentication refused.\r\n"); + tis_auth_refused = 1; + continue; } else { int challengelen = ((pktin.body[0] << 24) | (pktin.body[1] << 16) | @@ -2197,12 +2199,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) if (challengelen > sizeof(prompt) - 1) challengelen = sizeof(prompt) - 1; /* prevent overrun */ memcpy(prompt, pktin.body + 4, challengelen); - prompt[challengelen] = '\0'; + /* Prompt heuristic comes from OpenSSH */ + strncpy(prompt + challengelen, + memchr(prompt, '\n', challengelen) ? + "": "\r\nResponse: ", + (sizeof prompt) - challengelen); + prompt[(sizeof prompt) - 1] = '\0'; } } - if (pktin.type == SSH1_SMSG_FAILURE && - cfg.try_tis_auth && - (supported_auths_mask & (1 << SSH1_AUTH_CCARD))) { + if (cfg.try_tis_auth && + (supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !ccard_auth_refused) { pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; logevent("Requested CryptoCard authentication"); send_packet(SSH1_CMSG_AUTH_CCARD, PKT_END); @@ -2210,6 +2217,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) if (pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) { logevent("CryptoCard authentication declined"); c_write_str("CryptoCard authentication refused.\r\n"); + ccard_auth_refused = 1; + continue; } else { int challengelen = ((pktin.body[0] << 24) | (pktin.body[1] << 16) | @@ -2219,7 +2228,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) if (challengelen > sizeof(prompt) - 1) challengelen = sizeof(prompt) - 1; /* prevent overrun */ memcpy(prompt, pktin.body + 4, challengelen); - strncpy(prompt + challengelen, "\r\nResponse : ", + strncpy(prompt + challengelen, + memchr(prompt, '\n', challengelen) ? + "" : "\r\nResponse: ", sizeof(prompt) - challengelen); prompt[sizeof(prompt) - 1] = '\0'; } @@ -2241,6 +2252,11 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) sfree(comment); } + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ if (ssh_get_line) { if (!ssh_get_line(prompt, password, sizeof(password), TRUE)) { /* @@ -2256,7 +2272,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crReturn(1); } } else { - c_write_str(prompt); + /* Prompt may have come from server. We've munged it a bit, so + * we know it to be zero-terminated at least once. */ + c_write_untrusted(prompt, strlen(prompt)); pos = 0; ssh_send_ok = 1; while (pos >= 0) { @@ -2394,26 +2412,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) * use of the fact that the password is interpreted * as a C string: so we can append a NUL, then some * random data. + * + * One server (a Cisco one) can deal with neither + * SSH1_MSG_IGNORE _nor_ a padded password string. + * For this server we are left with no defences + * against password length sniffing. */ - if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) { - char string[64]; - char *s; - int len; - - len = strlen(password); - if (len < sizeof(string)) { - s = string; - strcpy(string, password); - len++; /* cover the zero byte */ - while (len < sizeof(string)) { - string[len++] = (char) random_byte(); - } - } else { - s = password; - } - send_packet(pwpkt_type, PKT_INT, len, - PKT_DATA, s, len, PKT_END); - } else { + if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + /* + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. + */ int bottom, top, pwlen, i; char *randomstr; @@ -2445,7 +2454,45 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) PKT_STR, randomstr, PKT_END); } } + logevent("Sending password with camouflage packets"); ssh_pkt_defersend(); + } + else if (!(ssh_remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + char string[64]; + char *s; + int len; + + len = strlen(password); + if (len < sizeof(string)) { + s = string; + strcpy(string, password); + len++; /* cover the zero byte */ + while (len < sizeof(string)) { + string[len++] = (char) random_byte(); + } + } else { + s = password; + } + logevent("Sending length-padded password"); + send_packet(pwpkt_type, PKT_INT, len, + PKT_DATA, s, len, PKT_END); + } else { + /* + * The server has _both_ + * BUG_CHOKES_ON_SSH1_IGNORE and + * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is + * therefore nothing we can do. + */ + int len; + len = strlen(password); + logevent("Sending unpadded password"); + send_packet(pwpkt_type, PKT_INT, len, + PKT_DATA, password, len, PKT_END); } } else { send_packet(pwpkt_type, PKT_STR, password, PKT_END); @@ -2476,19 +2523,29 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) void sshfwd_close(struct ssh_channel *c) { if (c && !c->closes) { - if (ssh_version == 1) { - send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, - PKT_END); - } else { - ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(c->remoteid); - ssh2_pkt_send(); + /* + * If the channel's remoteid is -1, we have sent + * CHANNEL_OPEN for this channel, but it hasn't even been + * acknowledged by the server. So we must set a close flag + * on it now, and then when the server acks the channel + * open, we can close it then. + */ + if (c->remoteid != -1) { + if (ssh_version == 1) { + send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + } else { + ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } } c->closes = 1; if (c->type == CHAN_X11) { c->u.x11.s = NULL; logevent("Forwarded X11 connection terminated"); - } else if (c->type == CHAN_SOCKDATA) { + } else if (c->type == CHAN_SOCKDATA || + c->type == CHAN_SOCKDATA_DORMANT) { c->u.pfd.s = NULL; logevent("Forwarded port closed"); } @@ -2714,7 +2771,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) if (eof_needed) ssh_special(TS_EOF); - ldisc_send(NULL, 0); /* cause ldisc to notice changes */ + ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; ssh_channels = newtree234(ssh_channelcmp); while (1) { @@ -2855,9 +2912,21 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) if (c && c->type == CHAN_SOCKDATA_DORMANT) { c->remoteid = localid; c->type = CHAN_SOCKDATA; + c->v.v1.throttling = 0; pfd_confirm(c->u.pfd.s); } + if (c && c->closes) { + /* + * We have a pending close on this channel, + * which we decided on before the server acked + * the channel open. So now we know the + * remoteid, we can close it again. + */ + send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + } + } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) { unsigned int remoteid = GET_32BIT(pktin.body); unsigned int localid = GET_32BIT(pktin.body+4); @@ -2984,6 +3053,15 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) * if no pty is available or in other odd cases. Ignore */ } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) { send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); + /* + * In case `helpful' firewalls or proxies tack + * extra human-readable text on the end of the + * session which we might mistake for another + * encrypted packet, we close the session once + * we've sent EXIT_CONFIRMATION. + */ + ssh_state = SSH_STATE_CLOSED; + crReturnV; } else { bombout(("Strange packet received: type %d", pktin.type)); crReturnV; @@ -4359,7 +4437,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Server got confused by X11 forwarding request")); + bombout(("Unexpected response to X11 forwarding request:" + " packet type %d", pktin.type)); crReturnV; } logevent("X11 forwarding refused"); @@ -4447,7 +4526,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (pktin.type != SSH2_MSG_REQUEST_SUCCESS) { if (pktin.type != SSH2_MSG_REQUEST_FAILURE) { - bombout(("Server got confused by port forwarding request")); + bombout(("Unexpected response to port " + "forwarding request: packet type %d", + pktin.type)); crReturnV; } logevent("Server refused this port forwarding"); @@ -4485,8 +4566,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { - bombout( - ("Server got confused by agent forwarding request")); + bombout(("Unexpected response to agent forwarding request:" + " packet type %d", pktin.type)); crReturnV; } logevent("Agent forwarding refused"); @@ -4528,7 +4609,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Server got confused by pty request")); + bombout(("Unexpected response to pty request:" + " packet type %d", pktin.type)); crReturnV; } c_write_str("Server refused to allocate pty\r\n"); @@ -4585,7 +4667,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Server got confused by shell/command request")); + bombout(("Unexpected response to shell/command request:" + " packet type %d", pktin.type)); crReturnV; } /* @@ -4616,7 +4699,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) /* * Transfer data! */ - ldisc_send(NULL, 0); /* cause ldisc to notice changes */ + ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; while (1) { static int try_send; @@ -4803,11 +4886,22 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) continue; /* dunno why they're confirming this */ c->remoteid = ssh2_pkt_getuint32(); c->type = CHAN_SOCKDATA; - c->closes = 0; c->v.v2.remwindow = ssh2_pkt_getuint32(); c->v.v2.remmaxpkt = ssh2_pkt_getuint32(); bufchain_init(&c->v.v2.outbuffer); - pfd_confirm(c->u.pfd.s); + if (c->u.pfd.s) + pfd_confirm(c->u.pfd.s); + if (c->closes) { + /* + * We have a pending close on this channel, + * which we decided on before the server acked + * the channel open. So now we know the + * remoteid, we can close it again. + */ + ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { unsigned i = ssh2_pkt_getuint32(); struct ssh_channel *c;