X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/7781f316d0d6aa3e286c588fb0efc832037e6866..d8d6c7e50e1fcf5171ec15f8a3e9bdcd141f0b64:/ssh.c diff --git a/ssh.c b/ssh.c index b270ac53..e0fed88b 100644 --- a/ssh.c +++ b/ssh.c @@ -15,9 +15,6 @@ #define TRUE 1 #endif -/* uncomment this for packet level debugging */ -/* #define DUMP_PACKETS */ - #define logevent(s) { logevent(s); \ if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \ { fprintf(stderr, "%s\n", s); fflush(stderr); } } @@ -112,6 +109,17 @@ #define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ #define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ +/* + * Packet type contexts, so that ssh2_pkt_type can correctly decode + * the ambiguous type numbers back into the correct type strings. + */ +#define SSH2_PKTCTX_DHGROUP1 0x0001 +#define SSH2_PKTCTX_DHGEX 0x0002 +#define SSH2_PKTCTX_PUBLICKEY 0x0010 +#define SSH2_PKTCTX_PASSWORD 0x0020 +#define SSH2_PKTCTX_KBDINTER 0x0040 +#define SSH2_PKTCTX_AUTH_MASK 0x00F0 + #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */ #define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */ #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */ @@ -161,6 +169,97 @@ static const char *const ssh2_disconnect_reasons[] = { #define BUG_SSH2_HMAC 2 #define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 +static int ssh_pkt_ctx = 0; + +#define translate(x) if (type == x) return #x +#define translatec(x,ctx) if (type == x && (ssh_pkt_ctx & ctx)) return #x +char *ssh1_pkt_type(int type) +{ + translate(SSH1_MSG_DISCONNECT); + translate(SSH1_SMSG_PUBLIC_KEY); + translate(SSH1_CMSG_SESSION_KEY); + translate(SSH1_CMSG_USER); + translate(SSH1_CMSG_AUTH_RSA); + translate(SSH1_SMSG_AUTH_RSA_CHALLENGE); + translate(SSH1_CMSG_AUTH_RSA_RESPONSE); + translate(SSH1_CMSG_AUTH_PASSWORD); + translate(SSH1_CMSG_REQUEST_PTY); + translate(SSH1_CMSG_WINDOW_SIZE); + translate(SSH1_CMSG_EXEC_SHELL); + translate(SSH1_CMSG_EXEC_CMD); + translate(SSH1_SMSG_SUCCESS); + translate(SSH1_SMSG_FAILURE); + translate(SSH1_CMSG_STDIN_DATA); + translate(SSH1_SMSG_STDOUT_DATA); + translate(SSH1_SMSG_STDERR_DATA); + translate(SSH1_CMSG_EOF); + translate(SSH1_SMSG_EXIT_STATUS); + translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH1_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH1_MSG_CHANNEL_DATA); + translate(SSH1_MSG_CHANNEL_CLOSE); + translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); + translate(SSH1_SMSG_X11_OPEN); + translate(SSH1_CMSG_PORT_FORWARD_REQUEST); + translate(SSH1_MSG_PORT_OPEN); + translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING); + translate(SSH1_SMSG_AGENT_OPEN); + translate(SSH1_MSG_IGNORE); + translate(SSH1_CMSG_EXIT_CONFIRMATION); + translate(SSH1_CMSG_X11_REQUEST_FORWARDING); + translate(SSH1_CMSG_AUTH_RHOSTS_RSA); + translate(SSH1_MSG_DEBUG); + translate(SSH1_CMSG_REQUEST_COMPRESSION); + translate(SSH1_CMSG_AUTH_TIS); + translate(SSH1_SMSG_AUTH_TIS_CHALLENGE); + translate(SSH1_CMSG_AUTH_TIS_RESPONSE); + translate(SSH1_CMSG_AUTH_CCARD); + translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE); + translate(SSH1_CMSG_AUTH_CCARD_RESPONSE); + return "unknown"; +} +char *ssh2_pkt_type(int type) +{ + translate(SSH2_MSG_DISCONNECT); + translate(SSH2_MSG_IGNORE); + translate(SSH2_MSG_UNIMPLEMENTED); + translate(SSH2_MSG_DEBUG); + translate(SSH2_MSG_SERVICE_REQUEST); + translate(SSH2_MSG_SERVICE_ACCEPT); + translate(SSH2_MSG_KEXINIT); + translate(SSH2_MSG_NEWKEYS); + translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1); + translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1); + translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX); + translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); + translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); + translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX); + translate(SSH2_MSG_USERAUTH_REQUEST); + translate(SSH2_MSG_USERAUTH_FAILURE); + translate(SSH2_MSG_USERAUTH_SUCCESS); + translate(SSH2_MSG_USERAUTH_BANNER); + translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY); + translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD); + translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER); + translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER); + translate(SSH2_MSG_GLOBAL_REQUEST); + translate(SSH2_MSG_REQUEST_SUCCESS); + translate(SSH2_MSG_REQUEST_FAILURE); + translate(SSH2_MSG_CHANNEL_OPEN); + translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + translate(SSH2_MSG_CHANNEL_OPEN_FAILURE); + translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + translate(SSH2_MSG_CHANNEL_DATA); + translate(SSH2_MSG_CHANNEL_EXTENDED_DATA); + translate(SSH2_MSG_CHANNEL_EOF); + translate(SSH2_MSG_CHANNEL_CLOSE); + translate(SSH2_MSG_CHANNEL_REQUEST); + translate(SSH2_MSG_CHANNEL_SUCCESS); + translate(SSH2_MSG_CHANNEL_FAILURE); + return "unknown"; +} +#undef translate +#undef translatec #define GET_32BIT(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 24) | \ @@ -235,18 +334,6 @@ extern void pfd_override_throttle(Socket s, int enable); #define SSH_MAX_BACKLOG 32768 #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.) - */ -const static struct ssh2_ciphers *ciphers[] = { - &ssh2_aes, - &ssh2_blowfish, - &ssh2_3des, -}; - const static struct ssh_kex *kex_algs[] = { &ssh_diffiehellman_gex, &ssh_diffiehellman @@ -412,6 +499,7 @@ static int ssh_echoing, ssh_editing; static tree234 *ssh_channels; /* indexed by local id */ static struct ssh_channel *mainchan; /* primary session channel */ +static int ssh_exitcode = -1; static tree234 *ssh_rportfwds; @@ -638,10 +726,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) if (cipher) cipher->decrypt(pktin.data, st->biglen); -#ifdef DUMP_PACKETS - debug(("Got packet len=%d pad=%d\n", st->len, st->pad)); - dmemdump(pktin.data, st->biglen); -#endif st->realcrc = crc32(pktin.data, st->biglen - 4); st->gotcrc = GET_32BIT(pktin.data + st->biglen - 4); @@ -655,10 +739,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) if (ssh1_compressing) { unsigned char *decompblk; int decomplen; -#ifdef DUMP_PACKETS - debug(("Packet payload pre-decompression:\n")); - dmemdump(pktin.body - 1, pktin.length + 1); -#endif zlib_decompress_block(pktin.body - 1, pktin.length + 1, &decompblk, &decomplen); @@ -673,12 +753,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) memcpy(pktin.body - 1, decompblk, decomplen); sfree(decompblk); pktin.length = decomplen - 1; -#ifdef DUMP_PACKETS - debug(("Packet payload post-decompression:\n")); - dmemdump(pktin.body - 1, pktin.length + 1); -#endif } + pktin.type = pktin.body[-1]; + + log_packet(PKT_INCOMING, pktin.type, ssh1_pkt_type(pktin.type), + pktin.body, pktin.length); + if (pktin.type == SSH1_SMSG_STDOUT_DATA || pktin.type == SSH1_SMSG_STDERR_DATA || pktin.type == SSH1_MSG_DEBUG || @@ -691,8 +772,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) } } - pktin.type = pktin.body[-1]; - if (pktin.type == SSH1_MSG_DEBUG) { /* log debug message */ char buf[80]; @@ -819,11 +898,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) sccipher->decrypt(pktin.data + st->cipherblk, st->packetlen - st->cipherblk); -#ifdef DUMP_PACKETS - debug(("Got packet len=%d pad=%d\n", st->len, st->pad)); - dmemdump(pktin.data, st->packetlen); -#endif - /* * Check the MAC. */ @@ -856,11 +930,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) } pktin.length = 5 + newlen; memcpy(pktin.data + 5, newpayload, newlen); -#ifdef DUMP_PACKETS - debug(("Post-decompression payload:\n")); - dmemdump(pktin.data + 5, newlen); -#endif - sfree(newpayload); } } @@ -868,6 +937,9 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) pktin.savedpos = 6; pktin.type = pktin.data[5]; + log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type), + pktin.data+6, pktin.length-6); + if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG) goto next_packet; /* FIXME: print DEBUG message */ @@ -942,13 +1014,12 @@ static int s_wrpkt_prepare(void) pktout.body[-1] = pktout.type; + log_packet(PKT_OUTGOING, pktout.type, ssh1_pkt_type(pktout.type), + pktout.body, pktout.length); + 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); @@ -966,10 +1037,6 @@ static int s_wrpkt_prepare(void) PUT_32BIT(pktout.data + biglen, crc); PUT_32BIT(pktout.data, len); -#ifdef DUMP_PACKETS - debug(("Sending packet len=%d\n", biglen + 4)); - dmemdump(pktout.data, biglen + 4); -#endif if (cipher) cipher->encrypt(pktout.data + 4, biglen); @@ -1233,18 +1300,15 @@ static int ssh2_pkt_construct(void) int cipherblk, maclen, padding, i; static unsigned long outgoing_sequence = 0; + log_packet(PKT_OUTGOING, pktout.data[5], ssh2_pkt_type(pktout.data[5]), + pktout.data + 6, pktout.length - 6); + /* * Compress packet payload. */ { 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; @@ -1273,11 +1337,6 @@ static int ssh2_pkt_construct(void) outgoing_sequence); outgoing_sequence++; /* whether or not we MACed */ -#ifdef DUMP_PACKETS - debug(("Sending packet len=%d\n", pktout.length + padding)); - dmemdump(pktout.data, pktout.length + padding); -#endif - if (cscipher) cscipher->encrypt(pktout.data, pktout.length + padding); @@ -1659,7 +1718,7 @@ static void ssh_sent(Plug plug, int bufsize) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static char *connect_to_host(char *host, int port, char **realhost) +static char *connect_to_host(char *host, int port, char **realhost, int nodelay) { static struct plug_function_table fn_table = { ssh_closing, @@ -1716,7 +1775,7 @@ static char *connect_to_host(char *host, int port, char **realhost) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 0, 1, &fn_table_ptr); + s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr); if ((err = sk_socket_error(s))) return err; @@ -3052,6 +3111,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) /* may be from EXEC_SHELL on some servers * if no pty is available or in other odd cases. Ignore */ } else if (pktin.type == SSH1_SMSG_EXIT_STATUS) { + char buf[100]; + ssh_exitcode = GET_32BIT(pktin.body); + sprintf(buf, "Server sent command exit status %d", + ssh_exitcode); + logevent(buf); send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); /* * In case `helpful' firewalls or proxies tack @@ -3155,6 +3219,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) static int n_preferred_ciphers; static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; static const struct ssh_compress *preferred_comp; + static int cipherstr_started; static int first_kex; crBegin; @@ -3172,7 +3237,10 @@ 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 */ + if (cfg.ssh2_des_cbc) { + preferred_ciphers[n_preferred_ciphers] = &ssh2_des; + n_preferred_ciphers++; + } break; case CIPHER_3DES: preferred_ciphers[n_preferred_ciphers] = &ssh2_3des; @@ -3232,24 +3300,28 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) } /* List client->server encryption algorithms. */ ssh2_pkt_addstring_start(); + cipherstr_started = 0; for (i = 0; i < n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = preferred_ciphers[i]; if (!c) continue; /* warning flag */ for (j = 0; j < c->nciphers; j++) { - ssh2_pkt_addstring_str(c->list[j]->name); - if (i < n_preferred_ciphers || j < c->nciphers - 1) + if (cipherstr_started) ssh2_pkt_addstring_str(","); + ssh2_pkt_addstring_str(c->list[j]->name); + cipherstr_started = 1; } } /* List server->client encryption algorithms. */ ssh2_pkt_addstring_start(); + cipherstr_started = 0; for (i = 0; i < n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = preferred_ciphers[i]; if (!c) continue; /* warning flag */ for (j = 0; j < c->nciphers; j++) { - ssh2_pkt_addstring_str(c->list[j]->name); - if (i < n_preferred_ciphers || j < c->nciphers - 1) + if (cipherstr_started) ssh2_pkt_addstring_str(","); + ssh2_pkt_addstring_str(c->list[j]->name); + cipherstr_started = 1; } } /* List client->server MAC algorithms. */ @@ -3353,6 +3425,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++) { @@ -3373,6 +3450,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)) { @@ -3429,6 +3511,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) */ if (kex == &ssh_diffiehellman_gex) { logevent("Doing Diffie-Hellman group exchange"); + ssh_pkt_ctx |= SSH2_PKTCTX_DHGEX; /* * Work out how big a DH group we will need to allow that * much data. @@ -3449,6 +3532,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; } else { + ssh_pkt_ctx |= SSH2_PKTCTX_DHGROUP1; dh_setup_group1(); kex_init_value = SSH2_MSG_KEXDH_INIT; kex_reply_value = SSH2_MSG_KEXDH_REPLY; @@ -3500,15 +3584,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.) */ @@ -3531,6 +3606,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; @@ -3706,6 +3790,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * the username they will want to be able to get back and * retype it! */ + username[0] = '\0'; do { static int pos; static char c; @@ -3714,7 +3799,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * Get a username. */ pos = 0; - if ((flags & FLAG_INTERACTIVE) && !*cfg.username) { + if (*username && !cfg.change_username) { + /* + * We got a username last time round this loop, and + * with change_username turned off we don't try to get + * it again. + */ + } else if ((flags & FLAG_INTERACTIVE) && !*cfg.username) { if (ssh_get_line) { if (!ssh_get_line("login as: ", username, sizeof(username), FALSE)) { @@ -3785,6 +3876,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * just in case it succeeds, and (b) so that we know what * authentication methods we can usefully try next. */ + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(username); ssh2_pkt_addstring("ssh-connection"); /* service requested */ @@ -3901,11 +3994,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) in_commasep_string("publickey", methods, methlen); can_passwd = in_commasep_string("password", methods, methlen); - can_keyb_inter = + can_keyb_inter = cfg.try_ki_auth && in_commasep_string("keyboard-interactive", methods, methlen); } method = 0; + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; if (!method && can_pubkey && agent_exists() && !tried_agent) { /* @@ -3917,6 +4011,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int authed = FALSE; void *r; + ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; + tried_agent = TRUE; logevent("Pageant is running. Requesting keys."); @@ -4053,6 +4149,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) tried_pubkey_config = TRUE; + ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; + /* * Try the public key supplied in the configuration. * @@ -4105,6 +4203,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) type = AUTH_TYPE_KEYBOARD_INTERACTIVE; tried_keyb_inter = TRUE; + ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER; + ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(username); ssh2_pkt_addstring("ssh-connection"); /* service requested */ @@ -4130,6 +4230,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) type = AUTH_TYPE_KEYBOARD_INTERACTIVE; tried_keyb_inter = TRUE; + ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER; + /* We've got packet with that "interactive" info dump banners, and set its prompt as ours */ { @@ -4146,6 +4248,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(); @@ -4154,6 +4258,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (!method && can_passwd) { method = AUTH_PASSWORD; + ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD; sprintf(pwprompt, "%.90s@%.90s's password: ", username, savedhost); need_pw = TRUE; @@ -4508,7 +4613,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); ssh2_pkt_addstring("tcpip-forward"); ssh2_pkt_addbool(1);/* want reply */ - ssh2_pkt_addstring("127.0.0.1"); + if (cfg.rport_acceptall) + ssh2_pkt_addstring("0.0.0.0"); + else + ssh2_pkt_addstring("127.0.0.1"); ssh2_pkt_adduint32(sport); ssh2_pkt_send(); @@ -4797,8 +4905,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; @@ -4858,12 +4964,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * See if that was the last channel left open. */ if (count234(ssh_channels) == 0) { +#if 0 + /* + * We used to send SSH_MSG_DISCONNECT here, + * because I'd believed that _every_ conforming + * SSH2 connection had to end with a disconnect + * being sent by at least one side; apparently + * I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut + * when you're done, and indeed OpenSSH feels + * this is more polite than sending a + * DISCONNECT. So now we don't. + */ logevent("All channels closed. Disconnecting"); ssh2_pkt_init(SSH2_MSG_DISCONNECT); ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION); ssh2_pkt_addstring("All open channels closed"); ssh2_pkt_addstring("en"); /* language tag */ ssh2_pkt_send(); +#endif ssh_state = SSH_STATE_CLOSED; crReturnV; } @@ -4917,6 +5036,67 @@ 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; + } + + /* + * Having got the channel number, we now look at + * the request type string to see if it's something + * we recognise. + */ + if (typelen == 11 && !memcmp(type, "exit-status", 11) && + c == mainchan) { + /* We recognise "exit-status" on the primary channel. */ + char buf[100]; + ssh_exitcode = ssh2_pkt_getuint32(); + sprintf(buf, "Server sent command exit status %d", + ssh_exitcode); + logevent(buf); + if (want_reply) { + ssh2_pkt_init(SSH2_MSG_CHANNEL_SUCCESS); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } + } else { + /* + * This is a channel request we don't know + * about, 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; @@ -5060,7 +5240,7 @@ static void ssh2_protocol(unsigned char *in, int inlen, int ispkt) * * Returns an error message, or NULL on success. */ -static char *ssh_init(char *host, int port, char **realhost) +static char *ssh_init(char *host, int port, char **realhost, int nodelay) { char *p; @@ -5076,7 +5256,7 @@ static char *ssh_init(char *host, int port, char **realhost) ssh_overall_bufsize = 0; ssh_fallback_cmd = 0; - p = connect_to_host(host, port, realhost); + p = connect_to_host(host, port, realhost, nodelay); if (p != NULL) return p; @@ -5290,6 +5470,11 @@ static int ssh_ldisc(int option) return FALSE; } +static int ssh_return_exitcode(void) +{ + return ssh_exitcode; +} + Backend ssh_backend = { ssh_init, ssh_send, @@ -5297,6 +5482,7 @@ Backend ssh_backend = { ssh_size, ssh_special, ssh_socket, + ssh_return_exitcode, ssh_sendok, ssh_ldisc, ssh_unthrottle,