X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/00db133f300dd6aa08111d2b42c749a940ecede9..247308b5cf8a51eba882abf13c5572ba8f83d0ea:/ssh.c diff --git a/ssh.c b/ssh.c index bc7f5bf6..51bbf44e 100644 --- a/ssh.c +++ b/ssh.c @@ -19,9 +19,21 @@ 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 */ @@ -168,6 +180,7 @@ 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 static int ssh_pkt_ctx = 0; @@ -309,6 +322,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: @@ -499,6 +524,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; @@ -723,6 +749,11 @@ 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); @@ -764,8 +795,8 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) 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); } @@ -774,12 +805,12 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen) 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) { @@ -798,7 +829,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); } @@ -939,36 +970,105 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen) 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 */ - - 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); + 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); @@ -1503,6 +1603,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)) { @@ -1681,6 +1791,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. */ @@ -1774,9 +1885,11 @@ static char *connect_to_host(char *host, int port, char **realhost, int nodelay) sprintf(buf, "Connecting to %.100s port %d", addrbuf, port); logevent(buf); } - s = sk_new(addr, port, 0, 1, nodelay, &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); @@ -1853,11 +1966,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; @@ -2066,8 +2181,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 <= '~') || @@ -2102,8 +2216,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]; @@ -2113,7 +2238,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. */ @@ -2123,6 +2248,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. */ @@ -2151,6 +2277,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); @@ -2325,6 +2456,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); @@ -2355,8 +2487,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) @@ -3110,6 +3241,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 @@ -3709,6 +3845,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); @@ -3743,8 +3887,11 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int we_are_in; static int num_prompts, 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; @@ -3785,6 +3932,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * retype it! */ username[0] = '\0'; + got_username = FALSE; do { static int pos; static char c; @@ -3793,7 +3941,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * Get a username. */ pos = 0; - if (*username && !cfg.change_username) { + 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 @@ -3839,8 +3987,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 <= '~') || @@ -3864,6 +4011,7 @@ 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) @@ -3885,6 +4033,12 @@ 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) { + publickey_blob = ssh2_userkey_loadpub(cfg.keyfile, NULL, + &publickey_bloblen); + } else + publickey_blob = NULL; while (1) { /* @@ -4040,6 +4194,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); @@ -4274,6 +4434,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; @@ -4306,8 +4467,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) @@ -4607,7 +4767,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(); @@ -5052,20 +5215,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh2_pkt_addstring(buf); ssh2_pkt_addstring("en"); /* language tag */ ssh2_pkt_send(); - connection_fatal(buf); + connection_fatal("%s", 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. + * Having got the channel number, we now look at + * the request type string to see if it's something + * we recognise. */ - if (want_reply) { - ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE); - ssh2_pkt_adduint32(c->remoteid); - ssh2_pkt_send(); + 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; @@ -5440,6 +5641,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, @@ -5447,6 +5653,7 @@ Backend ssh_backend = { ssh_size, ssh_special, ssh_socket, + ssh_return_exitcode, ssh_sendok, ssh_ldisc, ssh_unthrottle,