X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/ca35e638d722fb2ac315d9a9ac27b4de946f487b..5a71c4ea7cbe7888e3dc79cea7b06cf56b0fecb5:/ssh.c diff --git a/ssh.c b/ssh.c index 67ae0d42..1885088f 100644 --- a/ssh.c +++ b/ssh.c @@ -15,16 +15,25 @@ #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); } } +/* logevent, only printf-formatted. */ +void logeventf(char *fmt, ...) +{ + va_list ap; + char stuff[200]; + + va_start(ap, fmt); + vsprintf(stuff, fmt, ap); + va_end(ap); + logevent(stuff); +} + #define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \ (s ? sk_close(s), s = NULL : 0), \ - connection_fatal msg ) + logeventf msg, connection_fatal msg ) #define SSH1_MSG_DISCONNECT 1 /* 0x1 */ #define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ @@ -112,6 +121,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 */ @@ -160,7 +180,100 @@ 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 BUG_CHOKES_ON_RSA 8 +#define BUG_SSH2_RSA_PADDING 16 +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) | \ @@ -210,6 +323,18 @@ extern void pfd_confirm(Socket s); extern void pfd_unthrottle(Socket s); extern void pfd_override_throttle(Socket s, int enable); +static void ssh2_pkt_init(int pkt_type); +static void ssh2_pkt_addbool(unsigned char value); +static void ssh2_pkt_adduint32(unsigned long value); +static void ssh2_pkt_addstring_start(void); +static void ssh2_pkt_addstring_str(char *data); +static void ssh2_pkt_addstring_data(char *data, int len); +static void ssh2_pkt_addstring(char *data); +static char *ssh2_mpint_fmt(Bignum b, int *len); +static void ssh2_pkt_addmp(Bignum b); +static int ssh2_pkt_construct(void); +static void ssh2_pkt_send(void); + /* * Buffer management constants. There are several of these for * various different purposes: @@ -235,18 +360,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 @@ -402,8 +515,6 @@ static const struct ssh_compress *sccomp = NULL; static const struct ssh_kex *kex = NULL; static const struct ssh_signkey *hostkey = NULL; static unsigned char ssh2_session_id[20]; -int (*ssh_get_line) (const char *prompt, char *str, int maxlen, - int is_pw) = NULL; static char *savedhost; static int savedport; @@ -412,6 +523,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; @@ -636,12 +748,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) st->to_read -= st->chunk; } + if (cipher && detect_attack(pktin.data, st->biglen, NULL)) { + bombout(("Network attack (CRC compensation) detected!")); + crReturn(0); + } + 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 +768,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,35 +782,34 @@ 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 || pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE || pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - long strlen = GET_32BIT(pktin.body); - if (strlen + 4 != pktin.length) { + long stringlen = GET_32BIT(pktin.body); + if (stringlen + 4 != pktin.length) { bombout(("Received data packet with bogus string length")); crReturn(0); } } - pktin.type = pktin.body[-1]; - if (pktin.type == SSH1_MSG_DEBUG) { /* log debug message */ char buf[80]; - int strlen = GET_32BIT(pktin.body); + int stringlen = GET_32BIT(pktin.body); strcpy(buf, "Remote: "); - if (strlen > 70) - strlen = 70; - memcpy(buf + 8, pktin.body + 4, strlen); - buf[8 + strlen] = '\0'; + if (stringlen > 70) + stringlen = 70; + memcpy(buf + 8, pktin.body + 4, stringlen); + buf[8 + stringlen] = '\0'; logevent(buf); goto next_packet; } else if (pktin.type == SSH1_MSG_IGNORE) { @@ -720,7 +828,7 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) msglen = sizeof(buf) - nowlen - 1; memcpy(buf + nowlen, pktin.body + 4, msglen); buf[nowlen + msglen] = '\0'; - logevent(buf); + /* logevent(buf); (this is now done within the bombout macro) */ bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen)); crReturn(0); } @@ -819,11 +927,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 +959,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,36 +966,108 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) pktin.savedpos = 6; pktin.type = pktin.data[5]; - if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG) - goto next_packet; /* FIXME: print DEBUG message */ - - if (pktin.type == SSH2_MSG_DISCONNECT) { - /* log reason code in disconnect message */ - char buf[256]; - int reason = GET_32BIT(pktin.data + 6); - unsigned msglen = GET_32BIT(pktin.data + 10); - unsigned nowlen; - if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { - sprintf(buf, "Received disconnect message (%s)", - ssh2_disconnect_reasons[reason]); - } else { - sprintf(buf, "Received disconnect message (unknown type %d)", - reason); + log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type), + pktin.data+6, pktin.length-6); + + switch (pktin.type) { + /* + * These packets we must handle instantly. + */ + case SSH2_MSG_DISCONNECT: + { + /* log reason code in disconnect message */ + char buf[256]; + int reason = GET_32BIT(pktin.data + 6); + unsigned msglen = GET_32BIT(pktin.data + 10); + unsigned nowlen; + if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { + sprintf(buf, "Received disconnect message (%s)", + ssh2_disconnect_reasons[reason]); + } else { + sprintf(buf, "Received disconnect message (unknown type %d)", + reason); + } + logevent(buf); + strcpy(buf, "Disconnection message text: "); + nowlen = strlen(buf); + if (msglen > sizeof(buf) - nowlen - 1) + msglen = sizeof(buf) - nowlen - 1; + 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); + } + break; + case SSH2_MSG_IGNORE: + goto next_packet; + case SSH2_MSG_DEBUG: + { + /* log the debug message */ + char buf[512]; + /* int display = pktin.body[6]; */ + int stringlen = GET_32BIT(pktin.data+7); + int prefix; + strcpy(buf, "Remote debug message: "); + prefix = strlen(buf); + if (stringlen > sizeof(buf)-prefix-1) + stringlen = sizeof(buf)-prefix-1; + memcpy(buf + prefix, pktin.data + 11, stringlen); + buf[prefix + stringlen] = '\0'; + logevent(buf); } - logevent(buf); - strcpy(buf, "Disconnection message text: "); - nowlen = strlen(buf); - if (msglen > sizeof(buf) - nowlen - 1) - msglen = sizeof(buf) - nowlen - 1; - 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); + goto next_packet; /* FIXME: print the debug message */ + + /* + * These packets we need do nothing about here. + */ + case SSH2_MSG_UNIMPLEMENTED: + case SSH2_MSG_SERVICE_REQUEST: + case SSH2_MSG_SERVICE_ACCEPT: + case SSH2_MSG_KEXINIT: + case SSH2_MSG_NEWKEYS: + case SSH2_MSG_KEXDH_INIT: + case SSH2_MSG_KEXDH_REPLY: + /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */ + /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */ + case SSH2_MSG_KEX_DH_GEX_INIT: + case SSH2_MSG_KEX_DH_GEX_REPLY: + case SSH2_MSG_USERAUTH_REQUEST: + case SSH2_MSG_USERAUTH_FAILURE: + case SSH2_MSG_USERAUTH_SUCCESS: + case SSH2_MSG_USERAUTH_BANNER: + case SSH2_MSG_USERAUTH_PK_OK: + /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */ + /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */ + case SSH2_MSG_USERAUTH_INFO_RESPONSE: + case SSH2_MSG_GLOBAL_REQUEST: + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + case SSH2_MSG_CHANNEL_OPEN: + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + break; + + /* + * For anything else we send SSH2_MSG_UNIMPLEMENTED. + */ + default: + ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); + ssh2_pkt_adduint32(st->incoming_sequence - 1); + ssh2_pkt_send(); + break; } crFinish(0); @@ -942,13 +1112,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 +1135,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 +1398,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 +1435,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); @@ -1382,6 +1539,7 @@ static int ssh2_pkt_getbool(void) static void ssh2_pkt_getstring(char **p, int *length) { *p = NULL; + *length = 0; if (pktin.length - pktin.savedpos < 4) return; *length = GET_32BIT(pktin.data + pktin.savedpos); @@ -1409,6 +1567,75 @@ static Bignum ssh2_pkt_getmp(void) } /* + * Helper function to add an SSH2 signature blob to a packet. + * Expects to be shown the public key blob as well as the signature + * blob. Normally works just like ssh2_pkt_addstring, but will + * fiddle with the signature packet if necessary for + * BUG_SSH2_RSA_PADDING. + */ +static void ssh2_add_sigblob(void *pkblob_v, int pkblob_len, + void *sigblob_v, int sigblob_len) +{ + unsigned char *pkblob = (unsigned char *)pkblob_v; + unsigned char *sigblob = (unsigned char *)sigblob_v; + + /* dmemdump(pkblob, pkblob_len); */ + /* dmemdump(sigblob, sigblob_len); */ + + /* + * See if this is in fact an ssh-rsa signature and a buggy + * server; otherwise we can just do this the easy way. + */ + if ((ssh_remote_bugs & BUG_SSH2_RSA_PADDING) && + (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) { + int pos, len, siglen; + + /* + * Find the byte length of the modulus. + */ + + pos = 4+7; /* skip over "ssh-rsa" */ + pos += 4 + GET_32BIT(pkblob+pos); /* skip over exponent */ + len = GET_32BIT(pkblob+pos); /* find length of modulus */ + pos += 4; /* find modulus itself */ + while (len > 0 && pkblob[pos] == 0) + len--, pos++; + /* debug(("modulus length is %d\n", len)); */ + + /* + * Now find the signature integer. + */ + pos = 4+7; /* skip over "ssh-rsa" */ + siglen = GET_32BIT(sigblob+pos); + /* debug(("signature length is %d\n", siglen)); */ + + if (len != siglen) { + unsigned char newlen[4]; + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data(sigblob, pos); + /* dmemdump(sigblob, pos); */ + pos += 4; /* point to start of actual sig */ + PUT_32BIT(newlen, len); + ssh2_pkt_addstring_data(newlen, 4); + /* dmemdump(newlen, 4); */ + newlen[0] = 0; + while (len-- > siglen) { + ssh2_pkt_addstring_data(newlen, 1); + /* dmemdump(newlen, 1); */ + } + ssh2_pkt_addstring_data(sigblob+pos, siglen); + /* dmemdump(sigblob+pos, siglen); */ + return; + } + + /* Otherwise fall through and do it the easy way. */ + } + + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data(sigblob, sigblob_len); +} + +/* * Examine the remote side's version string and compare it against * a list of known buggy implementations. */ @@ -1445,6 +1672,16 @@ static void ssh_detect_bugs(char *vstring) logevent("We believe remote version needs a plain SSH1 password"); } + if (!strcmp(imp, "Cisco-1.25")) { + /* + * These versions apparently have no clue whatever about + * RSA authentication and will panic and die if they see + * an AUTH_RSA message. + */ + ssh_remote_bugs |= BUG_CHOKES_ON_RSA; + 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)) { @@ -1454,6 +1691,15 @@ static void ssh_detect_bugs(char *vstring) ssh_remote_bugs |= BUG_SSH2_HMAC; 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')) { + /* + * 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"); + } } static int do_ssh_init(unsigned char c) @@ -1554,6 +1800,12 @@ 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)); ssh_protocol = ssh1_protocol; @@ -1623,6 +1875,7 @@ static int ssh_closing(Plug plug, char *error_msg, int error_code, } if (error_msg) { /* A socket error has occurred. */ + logevent(error_msg); connection_fatal(error_msg); } else { /* Otherwise, the remote side closed the connection normally. */ @@ -1659,7 +1912,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,9 +1969,11 @@ 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); - if ((err = sk_socket_error(s))) + s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr); + if ((err = sk_socket_error(s))) { + s = NULL; return err; + } #ifdef FWHACK sk_write(s, "connect ", 8); @@ -1795,11 +2050,13 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) struct RSAKey servkey, hostkey; struct MD5Context md5c; static unsigned long supported_ciphers_mask, supported_auths_mask; - static int tried_publickey; + static int tried_publickey, tried_agent; static int tis_auth_refused, ccard_auth_refused; static unsigned char session_id[16]; static int cipher_type; static char username[100]; + static void *publickey_blob; + int publickey_bloblen; crBegin; @@ -1969,7 +2226,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static int pos = 0; static char c; if ((flags & FLAG_INTERACTIVE) && !*cfg.username) { - if (ssh_get_line) { + if (ssh_get_line && !ssh_getline_pw_only) { if (!ssh_get_line("login as: ", username, sizeof(username), FALSE)) { /* @@ -2008,8 +2265,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) break; case 3: case 4: - random_save_seed(); - exit(0); + cleanup_exit(0); break; default: if (((c >= ' ' && c <= '~') || @@ -2044,8 +2300,19 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crWaitUntil(ispkt); - tried_publickey = 0; + if ((ssh_remote_bugs & BUG_CHOKES_ON_RSA)) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + tried_publickey = tried_agent = 1; + } else { + tried_publickey = tried_agent = 0; + } tis_auth_refused = ccard_auth_refused = 0; + /* Load the public half of cfg.keyfile so we notice if it's in Pageant */ + if (*cfg.keyfile) { + if (!rsakey_pubblob(cfg.keyfile, &publickey_blob, &publickey_bloblen)) + publickey_blob = NULL; + } else + publickey_blob = NULL; while (pktin.type == SSH1_SMSG_FAILURE) { static char password[100]; @@ -2055,7 +2322,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static int pwpkt_type; pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - if (agent_exists()) { + if (agent_exists() && !tried_agent) { /* * Attempt RSA authentication using Pageant. */ @@ -2065,6 +2332,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static int authed = FALSE; void *r; + tried_agent = 1; logevent("Pageant is running. Requesting keys."); /* Request the keys held by the agent. */ @@ -2093,6 +2361,11 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) sprintf(buf, "Trying Pageant key #%d", i); logevent(buf); } + if (publickey_blob && + !memcmp(p, publickey_blob, publickey_bloblen)) { + logevent("This key matches configured key file"); + tried_publickey = 1; + } p += 4; p += ssh1_read_bignum(p, &key.exponent); p += ssh1_read_bignum(p, &key.modulus); @@ -2241,8 +2514,22 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) } if (pwpkt_type == SSH1_CMSG_AUTH_RSA) { char *comment = NULL; + int type; + char msgbuf[256]; if (flags & FLAG_VERBOSE) c_write_str("Trying public key authentication.\r\n"); + sprintf(msgbuf, "Trying public key \"%.200s\"", cfg.keyfile); + logevent(msgbuf); + type = key_type(cfg.keyfile); + if (type != SSH_KEYTYPE_SSH1) { + sprintf(msgbuf, "Key is of wrong type (%s)", + key_type_to_str(type)); + logevent(msgbuf); + c_write_str(msgbuf); + c_write_str("\r\n"); + tried_publickey = 1; + continue; + } if (!rsakey_encrypted(cfg.keyfile, &comment)) { if (flags & FLAG_VERBOSE) c_write_str("No passphrase required.\r\n"); @@ -2267,6 +2554,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) send_packet(SSH1_MSG_DISCONNECT, PKT_STR, "No more passwords available to try", PKT_END); + logevent("Unable to authenticate"); connection_fatal("Unable to authenticate"); ssh_state = SSH_STATE_CLOSED; crReturn(1); @@ -2297,8 +2585,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) break; case 3: case 4: - random_save_seed(); - exit(0); + cleanup_exit(0); break; default: if (pos < sizeof(password)-1) @@ -2523,19 +2810,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 (((int)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"); } @@ -2632,11 +2929,13 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) } { - char type, *e; + char type; + static char *e; int n; - int sport,dport; + int sport,dport,sserv,dserv; char sports[256], dports[256], host[256]; char buf[1024]; + struct servent *se; ssh_rportfwds = newtree234(ssh_rportcmp_ssh1); /* Add port forwardings. */ @@ -2661,12 +2960,43 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) dports[n] = 0; e++; dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + se = getservbyname(dports, NULL); + if (se != NULL) { + dport = ntohs(se->s_port); + } else { + sprintf(buf, + "Service lookup failed for destination port \"%s\"", + dports); + logevent(buf); + } + } sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + se = getservbyname(sports, NULL); + if (se != NULL) { + sport = ntohs(se->s_port); + } else { + sprintf(buf, + "Service lookup failed for source port \"%s\"", + sports); + logevent(buf); + } + } if (sport && dport) { if (type == 'L') { pfd_addforward(host, dport, sport); - sprintf(buf, "Local port %d forwarding to %s:%d", - sport, host, dport); + sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to" + " %s:%.*s%.*s%d%.*s", + sserv ? strlen(sports) : 0, sports, + sserv, "(", sport, sserv, ")", + host, + dserv ? strlen(dports) : 0, dports, + dserv, "(", dport, dserv, ")"); logevent(buf); } else { struct ssh_rportfwd *pf; @@ -2680,14 +3010,31 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) logevent(buf); sfree(pf); } else { - sprintf(buf, "Requesting remote port %d forward to %s:%d", - sport, host, dport); + sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s" + " forward to %s:%.*s%.*s%d%.*s", + sserv ? strlen(sports) : 0, sports, + sserv, "(", sport, sserv, ")", + host, + dserv ? strlen(dports) : 0, dports, + dserv, "(", dport, dserv, ")"); logevent(buf); send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST, PKT_INT, sport, PKT_STR, host, PKT_INT, dport, PKT_END); + do { + crReturnV; + } while (!ispkt); + if (pktin.type != SSH1_SMSG_SUCCESS + && pktin.type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crReturnV; + } else if (pktin.type == SSH1_SMSG_FAILURE) { + c_write_str("Server refused port forwarding\r\n"); + ssh_editing = ssh_echoing = 1; + } + logevent("Remote port forwarding enabled"); } } } @@ -2902,9 +3249,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); @@ -3030,6 +3389,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 @@ -3133,6 +3497,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; @@ -3150,7 +3515,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; @@ -3210,24 +3578,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. */ @@ -3331,6 +3703,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++) { @@ -3351,6 +3728,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)) { @@ -3407,6 +3789,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. @@ -3427,6 +3810,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; @@ -3478,15 +3862,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.) */ @@ -3509,6 +3884,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; @@ -3609,6 +3993,14 @@ static int ssh2_try_send(struct ssh_channel *c) */ static void ssh2_set_window(struct ssh_channel *c, unsigned newwin) { + /* + * Never send WINDOW_ADJUST for a channel that the remote side + * already thinks it's closed; there's no point, since it won't + * be sending any more data anyway. + */ + if (c->closes != 0) + return; + if (newwin > c->v.v2.locwindow) { ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); ssh2_pkt_adduint32(c->remoteid); @@ -3641,10 +4033,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int tried_pubkey_config, tried_agent, tried_keyb_inter; static int kbd_inter_running; static int we_are_in; - static int num_prompts, echo; + static int num_prompts, curr_prompt, echo; static char username[100]; + static int got_username; static char pwprompt[200]; static char password[100]; + static void *publickey_blob; + static int publickey_bloblen; crBegin; @@ -3684,6 +4079,8 @@ 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'; + got_username = FALSE; do { static int pos; static char c; @@ -3692,8 +4089,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * Get a username. */ pos = 0; - if ((flags & FLAG_INTERACTIVE) && !*cfg.username) { - if (ssh_get_line) { + if (got_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 && !ssh_getline_pw_only) { if (!ssh_get_line("login as: ", username, sizeof(username), FALSE)) { /* @@ -3732,8 +4135,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) break; case 3: case 4: - random_save_seed(); - exit(0); + cleanup_exit(0); break; default: if (((c >= ' ' && c <= '~') || @@ -3757,12 +4159,15 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) c_write_str(stuff); } } + got_username = TRUE; /* * Send an authentication request using method "none": (a) * 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 */ @@ -3776,6 +4181,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) tried_agent = FALSE; tried_keyb_inter = FALSE; kbd_inter_running = FALSE; + /* Load the pub half of cfg.keyfile so we notice if it's in Pageant */ + if (*cfg.keyfile) { + int keytype; + logeventf("Reading private key file \"%.150s\"", cfg.keyfile); + keytype = key_type(cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH2) + publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL, + &publickey_bloblen); + else { + char msgbuf[256]; + logeventf("Unable to use this key file (%s)", + key_type_to_str(keytype)); + sprintf(msgbuf, "Unable to use key file \"%.150s\" (%s)\r\n", + cfg.keyfile, key_type_to_str(keytype)); + c_write_str(msgbuf); + publickey_blob = NULL; + } + } else + publickey_blob = NULL; while (1) { /* @@ -3810,9 +4234,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (kbd_inter_running && pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) { /* - * This is a further prompt in keyboard-interactive - * authentication. Do nothing. + * This is either a further set-of-prompts packet + * in keyboard-interactive authentication, or it's + * the same one and we came back here with `gotit' + * set. In the former case, we must reset the + * curr_prompt variable. */ + if (!gotit) + curr_prompt = 0; } else if (pktin.type != SSH2_MSG_USERAUTH_FAILURE) { bombout(("Strange packet received during authentication: type %d", pktin.type)); @@ -3879,11 +4308,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) { /* @@ -3895,6 +4325,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int authed = FALSE; void *r; + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; + tried_agent = TRUE; logevent("Pageant is running. Requesting keys."); @@ -3928,6 +4361,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } pklen = GET_32BIT(p); p += 4; + if (publickey_blob && + pklen == publickey_bloblen && + !memcmp(p, publickey_blob, publickey_bloblen)) { + logevent("This key matches configured key file"); + tried_pubkey_config = 1; + } pkblob = p; p += pklen; alglen = GET_32BIT(pkblob); @@ -4004,10 +4443,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (ret) { if (ret[4] == SSH2_AGENT_SIGN_RESPONSE) { logevent("Sending Pageant's response"); - ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_data(ret + 9, - GET_32BIT(ret + - 5)); + ssh2_add_sigblob(pkblob, pklen, + ret + 9, GET_32BIT(ret + 5)); ssh2_pkt_send(); authed = TRUE; break; @@ -4023,7 +4460,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } } - if (!method && can_pubkey && *cfg.keyfile + if (!method && can_pubkey && publickey_blob && !tried_pubkey_config) { unsigned char *pub_blob; char *algorithm, *comment; @@ -4031,6 +4468,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) tried_pubkey_config = TRUE; + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; + /* * Try the public key supplied in the configuration. * @@ -4083,6 +4523,9 @@ 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_AUTH_MASK; + ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER; + ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(username); ssh2_pkt_addstring("ssh-connection"); /* service requested */ @@ -4101,6 +4544,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } kbd_inter_running = TRUE; + curr_prompt = 0; } if (kbd_inter_running) { @@ -4108,30 +4552,59 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) type = AUTH_TYPE_KEYBOARD_INTERACTIVE; tried_keyb_inter = TRUE; - /* We've got packet with that "interactive" info - dump banners, and set its prompt as ours */ - { - char *name, *inst, *lang, *prompt; - int name_len, inst_len, lang_len, prompt_len; + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER; + + if (curr_prompt == 0) { + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Display header data, and start going through + * the prompts. + */ + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + ssh2_pkt_getstring(&name, &name_len); ssh2_pkt_getstring(&inst, &inst_len); ssh2_pkt_getstring(&lang, &lang_len); - if (name_len > 0) + if (name_len > 0) { c_write_untrusted(name, name_len); - if (inst_len > 0) + c_write_str("\n"); + } + if (inst_len > 0) { c_write_untrusted(inst, inst_len); + c_write_str("\n"); + } num_prompts = ssh2_pkt_getuint32(); + } - ssh2_pkt_getstring(&prompt, &prompt_len); - strncpy(pwprompt, prompt, sizeof(pwprompt)); - need_pw = TRUE; + /* + * If there are prompts remaining in the packet, + * display one and get a response. + */ + if (curr_prompt < num_prompts) { + char *prompt; + int prompt_len; + ssh2_pkt_getstring(&prompt, &prompt_len); + if (prompt_len > 0) { + strncpy(pwprompt, prompt, sizeof(pwprompt)); + pwprompt[prompt_len < sizeof(pwprompt) ? + prompt_len : sizeof(pwprompt)-1] = '\0'; + } else { + strcpy(pwprompt, + ": "); + } echo = ssh2_pkt_getbool(); - } + need_pw = TRUE; + } else + need_pw = FALSE; } if (!method && can_passwd) { method = AUTH_PASSWORD; + ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD; sprintf(pwprompt, "%.90s@%.90s's password: ", username, savedhost); need_pw = TRUE; @@ -4153,6 +4626,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ("No more passwords available to try"); ssh2_pkt_addstring("en"); /* language tag */ ssh2_pkt_send(); + logevent("Unable to authenticate"); connection_fatal("Unable to authenticate"); ssh_state = SSH_STATE_CLOSED; crReturnV; @@ -4185,8 +4659,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) break; case 3: case 4: - random_save_seed(); - exit(0); + cleanup_exit(0); break; default: if (pos < sizeof(password)-1) @@ -4221,8 +4694,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_send(); type = AUTH_TYPE_NONE; } else { - unsigned char *blob, *sigdata; - int blob_len, sigdata_len; + unsigned char *pkblob, *sigblob, *sigdata; + int pkblob_len, sigblob_len, sigdata_len; /* * We have loaded the private key and the server @@ -4235,10 +4708,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_addstring("publickey"); /* method */ ssh2_pkt_addbool(TRUE); ssh2_pkt_addstring(key->alg->name); - blob = key->alg->public_blob(key->data, &blob_len); + pkblob = key->alg->public_blob(key->data, &pkblob_len); ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_data(blob, blob_len); - sfree(blob); + ssh2_pkt_addstring_data(pkblob, pkblob_len); /* * The data to be signed is: @@ -4254,12 +4726,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) memcpy(sigdata + 4, ssh2_session_id, 20); memcpy(sigdata + 24, pktout.data + 5, pktout.length - 5); - blob = - key->alg->sign(key->data, sigdata, sigdata_len, - &blob_len); - ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_data(blob, blob_len); - sfree(blob); + sigblob = key->alg->sign(key->data, sigdata, + sigdata_len, &sigblob_len); + ssh2_add_sigblob(pkblob, pkblob_len, + sigblob, sigblob_len); + sfree(pkblob); + sfree(sigblob); sfree(sigdata); ssh2_pkt_send(); @@ -4323,11 +4795,28 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) logevent("Sent password"); type = AUTH_TYPE_PASSWORD; } else if (method == AUTH_KEYBOARD_INTERACTIVE) { - ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - ssh2_pkt_adduint32(num_prompts); - ssh2_pkt_addstring(password); - memset(password, 0, sizeof(password)); - ssh2_pkt_send(); + if (curr_prompt == 0) { + ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_pkt_adduint32(num_prompts); + } + if (need_pw) { /* only add pw if we just got one! */ + ssh2_pkt_addstring(password); + memset(password, 0, sizeof(password)); + curr_prompt++; + } + if (curr_prompt >= num_prompts) { + ssh2_pkt_send(); + } else { + /* + * If there are prompts remaining, we set + * `gotit' so that we won't attempt to get + * another packet. Then we go back round the + * loop and will end up retrieving another + * prompt out of the existing packet. Funky or + * what? + */ + gotit = TRUE; + } type = AUTH_TYPE_KEYBOARD_INTERACTIVE; } else { c_write_str @@ -4433,9 +4922,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static char *e; /* preserve across crReturn */ char type; int n; - int sport,dport; + int sport,dport,sserv,dserv; char sports[256], dports[256], host[256]; char buf[1024]; + struct servent *se; ssh_rportfwds = newtree234(ssh_rportcmp_ssh2); /* Add port forwardings. */ @@ -4460,12 +4950,43 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) dports[n] = 0; e++; dport = atoi(dports); + dserv = 0; + if (dport == 0) { + dserv = 1; + se = getservbyname(dports, NULL); + if (se != NULL) { + dport = ntohs(se->s_port); + } else { + sprintf(buf, + "Service lookup failed for destination port \"%s\"", + dports); + logevent(buf); + } + } sport = atoi(sports); + sserv = 0; + if (sport == 0) { + sserv = 1; + se = getservbyname(sports, NULL); + if (se != NULL) { + sport = ntohs(se->s_port); + } else { + sprintf(buf, + "Service lookup failed for source port \"%s\"", + sports); + logevent(buf); + } + } if (sport && dport) { if (type == 'L') { pfd_addforward(host, dport, sport); - sprintf(buf, "Local port %d forwarding to %s:%d", - sport, host, dport); + sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to" + " %s:%.*s%.*s%d%.*s", + sserv ? strlen(sports) : 0, sports, + sserv, "(", sport, sserv, ")", + host, + dserv ? strlen(dports) : 0, dports, + dserv, "(", dport, dserv, ")"); logevent(buf); } else { struct ssh_rportfwd *pf; @@ -4480,13 +5001,21 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) logevent(buf); sfree(pf); } else { - sprintf(buf, "Requesting remote port %d (forwarded to %s:%d)", - sport, host, dport); + sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s" + " forward to %s:%.*s%.*s%d%.*s", + sserv ? strlen(sports) : 0, sports, + sserv, "(", sport, sserv, ")", + host, + dserv ? strlen(dports) : 0, dports, + dserv, "(", dport, dserv, ")"); logevent(buf); 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(); @@ -4775,8 +5304,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; @@ -4836,12 +5363,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; } @@ -4864,11 +5404,21 @@ 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; @@ -4884,6 +5434,84 @@ 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("%s", 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_GLOBAL_REQUEST) { + char *type; + int typelen, want_reply; + + ssh2_pkt_getstring(&type, &typelen); + want_reply = ssh2_pkt_getbool(); + + /* + * We currently don't support any global requests + * at all, so we either ignore the request or + * respond with REQUEST_FAILURE, depending on + * want_reply. + */ + if (want_reply) { + ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); + ssh2_pkt_send(); + } } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) { char *type; int typelen; @@ -5027,7 +5655,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; @@ -5043,7 +5671,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; @@ -5179,6 +5807,7 @@ void *new_sock_channel(Socket s) c->closes = 0; c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */ c->u.pfd.s = s; + bufchain_init(&c->v.v2.outbuffer); add234(ssh_channels, c); } return c; @@ -5257,6 +5886,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, @@ -5264,6 +5898,7 @@ Backend ssh_backend = { ssh_size, ssh_special, ssh_socket, + ssh_return_exitcode, ssh_sendok, ssh_ldisc, ssh_unthrottle,