X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/3ad9d396e3e57477b4da4b20665ca33edd5d7f67..2c2f4dba28ea405dd544aecfd752cd9c99760828:/ssh.c diff --git a/ssh.c b/ssh.c index c03cd6db..baa811fb 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) | \ @@ -234,15 +236,13 @@ extern void pfd_override_throttle(Socket s, int enable); #define OUR_V2_WINSIZE 16384 /* - * Ciphers for SSH2. We miss out single-DES because it isn't - * supported; also 3DES and Blowfish are both done differently from - * SSH1. (3DES uses outer chaining; Blowfish has the opposite - * endianness and different-sized keys.) + * Ciphers for SSH2. */ const static struct ssh2_ciphers *ciphers[] = { &ssh2_aes, &ssh2_blowfish, &ssh2_3des, + &ssh2_des, }; const static struct ssh_kex *kex_algs[] = { @@ -607,12 +607,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; @@ -725,6 +719,8 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) memcpy(buf + nowlen, pktin.body + 4, msglen); buf[nowlen + msglen] = '\0'; logevent(buf); + bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen)); + crReturn(0); } crFinish(0); @@ -768,11 +764,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); @@ -898,6 +890,12 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) memcpy(buf + nowlen, pktin.data + 14, msglen); buf[nowlen + msglen] = '\0'; logevent(buf); + bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"", + reason, + (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown", + buf+nowlen)); + crReturn(0); } crFinish(0); @@ -942,23 +940,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 */ @@ -1241,13 +1234,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; @@ -1420,17 +1415,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 @@ -1440,6 +1433,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)) { @@ -1481,8 +1484,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; @@ -1508,10 +1511,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); /* @@ -1594,6 +1597,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; @@ -1788,6 +1794,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]; @@ -2036,6 +2043,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]; @@ -2043,12 +2051,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. @@ -2171,9 +2175,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); @@ -2182,6 +2186,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) | @@ -2191,12 +2197,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); @@ -2204,6 +2215,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) | @@ -2213,7 +2226,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'; } @@ -2235,6 +2250,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)) { /* @@ -2250,7 +2270,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) { @@ -2299,7 +2321,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) tried_publickey = 1; i = loadrsakey(cfg.keyfile, &pubkey, password); if (i == 0) { - c_write_str("Couldn't load public key from "); + c_write_str("Couldn't load private key from "); c_write_str(cfg.keyfile); c_write_str(".\r\n"); continue; /* go and try password */ @@ -2388,26 +2410,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; @@ -2439,7 +2452,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); @@ -2470,19 +2521,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"); } @@ -2708,7 +2769,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) { @@ -2849,9 +2910,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); @@ -2978,6 +3051,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; @@ -3088,7 +3170,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) n_preferred_ciphers++; break; case CIPHER_DES: - /* Not supported in SSH2; silently drop */ + preferred_ciphers[n_preferred_ciphers] = &ssh2_des; + n_preferred_ciphers++; break; case CIPHER_3DES: preferred_ciphers[n_preferred_ciphers] = &ssh2_3des; @@ -3269,6 +3352,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) break; } } + if (!cscipher_tobe) { + bombout(("Couldn't agree a client-to-server cipher (available: %s)", str)); + crReturn(0); + } + ssh2_pkt_getstring(&str, &len); /* server->client cipher */ warn = 0; for (i = 0; i < n_preferred_ciphers; i++) { @@ -3289,6 +3377,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) break; } } + if (!sccipher_tobe) { + bombout(("Couldn't agree a server-to-client cipher (available: %s)", str)); + crReturn(0); + } + ssh2_pkt_getstring(&str, &len); /* client->server mac */ for (i = 0; i < nmacs; i++) { if (in_commasep_string(maclist[i]->name, str, len)) { @@ -3416,15 +3509,6 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) } /* - * Expect SSH2_MSG_NEWKEYS from server. - */ - crWaitUntil(ispkt); - if (pktin.type != SSH2_MSG_NEWKEYS) { - bombout(("expected new-keys packet from server")); - crReturn(0); - } - - /* * Authenticate remote host: verify host key. (We've already * checked the signature of the exchange hash.) */ @@ -3447,6 +3531,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) ssh2_pkt_send(); /* + * Expect SSH2_MSG_NEWKEYS from server. + */ + crWaitUntil(ispkt); + if (pktin.type != SSH2_MSG_NEWKEYS) { + bombout(("expected new-keys packet from server")); + crReturn(0); + } + + /* * Create and initialise session keys. */ cscipher = cscipher_tobe; @@ -4062,6 +4155,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_getstring(&prompt, &prompt_len); strncpy(pwprompt, prompt, sizeof(pwprompt)); + pwprompt[prompt_len < sizeof(pwprompt) ? + prompt_len : sizeof(pwprompt)-1] = '\0'; need_pw = TRUE; echo = ssh2_pkt_getbool(); @@ -4353,7 +4448,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"); @@ -4441,7 +4537,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"); @@ -4479,8 +4577,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"); @@ -4522,7 +4620,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"); @@ -4579,7 +4678,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; } /* @@ -4610,7 +4710,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; @@ -4708,8 +4808,6 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh_state = SSH_STATE_CLOSED; logevent("Received disconnect message"); crReturnV; - } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) { - continue; /* exit status et al; ignore (FIXME?) */ } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) { unsigned i = ssh2_pkt_getuint32(); struct ssh_channel *c; @@ -4797,11 +4895,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; @@ -4817,6 +4926,46 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) del234(ssh_channels, c); sfree(c); + } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) { + unsigned localid; + char *type; + int typelen, want_reply; + struct ssh_channel *c; + + localid = ssh2_pkt_getuint32(); + ssh2_pkt_getstring(&type, &typelen); + want_reply = ssh2_pkt_getbool(); + + /* + * First, check that the channel exists. Otherwise, + * we can instantly disconnect with a rude message. + */ + c = find234(ssh_channels, &localid, ssh_channelfind); + if (!c) { + char buf[80]; + sprintf(buf, "Received channel request for nonexistent" + " channel %d", localid); + logevent(buf); + ssh2_pkt_init(SSH2_MSG_DISCONNECT); + ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION); + ssh2_pkt_addstring(buf); + ssh2_pkt_addstring("en"); /* language tag */ + ssh2_pkt_send(); + connection_fatal(buf); + ssh_state = SSH_STATE_CLOSED; + crReturnV; + } + + /* + * We don't recognise any form of channel request, + * so we now either ignore the request or respond + * with CHANNEL_FAILURE, depending on want_reply. + */ + if (want_reply) { + ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) { char *type; int typelen;