X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/783415f807e7a983887b84b3515dadcd3558d60d..0d694692c59504838ec2043ddfe670b3a9247faf:/ssh.c diff --git a/ssh.c b/ssh.c index e32f51e9..033a0d27 100644 --- a/ssh.c +++ b/ssh.c @@ -19,8 +19,9 @@ if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \ fprintf(stderr, "%s\n", s); } -#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, sk_close(s), \ - s = NULL, connection_fatal msg ) +#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \ + (s ? sk_close(s), s = NULL : 0), \ + connection_fatal msg ) #define SSH1_MSG_DISCONNECT 1 /* 0x1 */ #define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ @@ -165,7 +166,7 @@ extern const struct ssh_cipher ssh_des; extern const struct ssh_cipher ssh_blowfish_ssh1; extern const struct ssh_cipher ssh_blowfish_ssh2; -extern char *x11_init (Socket *, char *, void *, char **); +extern char *x11_init (Socket *, char *, void *); extern void x11_close (Socket); extern void x11_send (Socket , char *, int); extern void x11_invent_auth(char *, int, char *, int); @@ -272,6 +273,7 @@ int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL; static char *savedhost; static int savedport; static int ssh_send_ok; +static int ssh_echoing, ssh_editing; static tree234 *ssh_channels; /* indexed by local id */ static struct ssh_channel *mainchan; /* primary session channel */ @@ -288,6 +290,8 @@ static int size_needed = FALSE, eof_needed = FALSE; static struct Packet pktin = { 0, 0, NULL, NULL, 0 }; static struct Packet pktout = { 0, 0, NULL, NULL, 0 }; +static unsigned char *deferred_send_data = NULL; +static int deferred_len = 0, deferred_size = 0; static int ssh_version; static void (*ssh_protocol)(unsigned char *in, int inlen, int ispkt); @@ -889,7 +893,13 @@ static void ssh2_pkt_addmp(Bignum b) { ssh2_pkt_addstring_data(p, len); sfree(p); } -static void ssh2_pkt_send(void) { + +/* + * Construct an SSH2 final-form packet: compress it, encrypt it, + * put the MAC on it. Final packet, ready to be sent, is stored in + * pktout.data. Total length is returned. + */ +static int ssh2_pkt_construct(void) { int cipherblk, maclen, padding, i; static unsigned long outgoing_sequence = 0; @@ -942,7 +952,48 @@ static void ssh2_pkt_send(void) { if (cscipher) cscipher->encrypt(pktout.data, pktout.length + padding); - sk_write(s, pktout.data, pktout.length + padding + maclen); + /* Ready-to-send packet starts at pktout.data. We return length. */ + return pktout.length + padding + maclen; +} + +/* + * Construct and send an SSH2 packet immediately. + */ +static void ssh2_pkt_send(void) { + int len = ssh2_pkt_construct(); + sk_write(s, pktout.data, len); +} + +/* + * Construct an SSH2 packet and add it to a deferred data block. + * Useful for sending multiple packets in a single sk_write() call, + * to prevent a traffic-analysing listener from being able to work + * out the length of any particular packet (such as the password + * packet). + * + * Note that because SSH2 sequence-numbers its packets, this can + * NOT be used as an m4-style `defer' allowing packets to be + * constructed in one order and sent in another. + */ +static void ssh2_pkt_defer(void) { + int len = ssh2_pkt_construct(); + if (deferred_len + len > deferred_size) { + deferred_size = deferred_len + len + 128; + deferred_send_data = srealloc(deferred_send_data, deferred_size); + } + memcpy(deferred_send_data+deferred_len, pktout.data, len); + deferred_len += len; +} + +/* + * Send the whole deferred data block constructed by + * ssh2_pkt_defer(). + */ +static void ssh2_pkt_defersend(void) { + sk_write(s, deferred_send_data, deferred_len); + deferred_len = deferred_size = 0; + sfree(deferred_send_data); + deferred_send_data = NULL; } #if 0 @@ -1148,7 +1199,14 @@ static void ssh_gotdata(unsigned char *data, int datalen) } static int ssh_receive(Socket skt, int urgent, char *data, int len) { - if (!len) { + if (urgent==3) { + /* A socket error has occurred. */ + ssh_state = SSH_STATE_CLOSED; + sk_close(s); + s = NULL; + connection_fatal(data); + return 0; + } else if (!len) { /* Connection has closed. */ ssh_state = SSH_STATE_CLOSED; sk_close(s); @@ -1210,7 +1268,7 @@ static char *connect_to_host(char *host, int port, char **realhost) /* * Open socket. */ - s = sk_new(addr, port, 0, ssh_receive); + s = sk_new(addr, port, 0, 1, ssh_receive); if ( (err = sk_socket_error(s)) ) return err; @@ -1835,8 +1893,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { crReturnV; } else if (pktin.type == SSH1_SMSG_FAILURE) { c_write("Server refused to allocate pty\r\n", 32); + ssh_editing = ssh_echoing = 1; } logevent("Allocated pty"); + } else { + ssh_editing = ssh_echoing = 1; } if (cfg.compression) { @@ -1866,9 +1927,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { if (eof_needed) ssh_special(TS_EOF); + ldisc_send(NULL, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; ssh_channels = newtree234(ssh_channelcmp); - begin_session(); while (1) { crReturnV; if (ispkt) { @@ -1896,11 +1957,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { PKT_END); logevent("Rejected X11 connect request"); } else { - char *rh; - c = smalloc(sizeof(struct ssh_channel)); - if ( x11_init(&c->u.x11.s, cfg.x11_display, c, &rh) != NULL ) { + if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) { logevent("opening X11 forward connection failed"); sfree(c); send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE, @@ -2087,14 +2146,14 @@ static int in_commasep_string(char *needle, char *haystack, int haylen) { /* * SSH2 key creation method. */ -static void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) { +static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, char *keyspace) { SHA_State s; /* First 20 bytes. */ SHA_Init(&s); sha_mpint(&s, K); SHA_Bytes(&s, H, 20); SHA_Bytes(&s, &chr, 1); - SHA_Bytes(&s, H, 20); + SHA_Bytes(&s, sessid, 20); SHA_Final(&s, keyspace); /* Next 20 bytes. */ SHA_Init(&s); @@ -2124,6 +2183,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) static int hostkeylen, siglen; static void *hkey; /* actual host key */ static unsigned char exchange_hash[20]; + static unsigned char first_exchange_hash[20]; static unsigned char keyspace[40]; static const struct ssh_cipher *preferred_cipher; static const struct ssh_compress *preferred_comp; @@ -2380,8 +2440,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) fingerprint = hostkey->fingerprint(hkey); verify_ssh_host_key(savedhost, savedport, hostkey->keytype, keystr, fingerprint); - logevent("Host key fingerprint is:"); - logevent(fingerprint); + if (first_kex) { /* don't bother logging this in rekeys */ + logevent("Host key fingerprint is:"); + logevent(fingerprint); + } sfree(fingerprint); sfree(keystr); hostkey->freekey(hkey); @@ -2404,14 +2466,23 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) cscomp->compress_init(); sccomp->decompress_init(); /* - * Set IVs after keys. + * Set IVs after keys. Here we use the exchange hash from the + * _first_ key exchange. */ - ssh2_mkkey(K, exchange_hash, 'C', keyspace); cscipher->setcskey(keyspace); - ssh2_mkkey(K, exchange_hash, 'D', keyspace); sccipher->setsckey(keyspace); - ssh2_mkkey(K, exchange_hash, 'A', keyspace); cscipher->setcsiv(keyspace); - ssh2_mkkey(K, exchange_hash, 'B', keyspace); sccipher->setsciv(keyspace); - ssh2_mkkey(K, exchange_hash, 'E', keyspace); csmac->setcskey(keyspace); - ssh2_mkkey(K, exchange_hash, 'F', keyspace); scmac->setsckey(keyspace); + if (first_kex) + memcpy(first_exchange_hash, exchange_hash, sizeof(exchange_hash)); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'C', keyspace); + cscipher->setcskey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'D', keyspace); + sccipher->setsckey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'A', keyspace); + cscipher->setcsiv(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'B', keyspace); + sccipher->setsciv(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'E', keyspace); + csmac->setcskey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'F', keyspace); + scmac->setsckey(keyspace); /* * If this is the first key exchange phase, we must pass the @@ -2432,9 +2503,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) * transport. If we ever see a KEXINIT, we must go back to the * start. */ - do { + while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT)) { crReturn(1); - } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT)); + } + logevent("Server initiated key re-exchange"); goto begin_key_exchange; crFinish(1); @@ -2609,13 +2681,46 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) c_write("\r\n", 2); } + /* + * We send the password packet lumped tightly together with + * an SSH_MSG_IGNORE packet. The IGNORE packet contains a + * string long enough to make the total length of the two + * packets constant. This should ensure that a passive + * listener doing traffic analyis can't work out the length + * of the password. + * + * For this to work, we need an assumption about the + * maximum length of the password packet. I think 256 is + * pretty conservative. Anyone using a password longer than + * that probably doesn't have much to worry about from + * people who find out how long their password is! + */ ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(username); ssh2_pkt_addstring("ssh-connection"); /* service requested */ ssh2_pkt_addstring("password"); ssh2_pkt_addbool(FALSE); ssh2_pkt_addstring(password); - ssh2_pkt_send(); + ssh2_pkt_defer(); + /* + * We'll include a string that's an exact multiple of the + * cipher block size. If the cipher is NULL for some + * reason, we don't do this trick at all because we gain + * nothing by it. + */ + if (cscipher) { + int i, j; + ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(); + for (i = deferred_len; i <= 256; i += cscipher->blksize) { + for (j = 0; j < cscipher->blksize; j++) { + char c = (char)random_byte(); + ssh2_pkt_addstring_data(&c, 1); + } + } + ssh2_pkt_defer(); + } + ssh2_pkt_defersend(); crWaitUntilV(ispkt); if (pktin.type != SSH2_MSG_USERAUTH_SUCCESS) { @@ -2740,9 +2845,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) crReturnV; } c_write("Server refused to allocate pty\r\n", 32); + ssh_editing = ssh_echoing = 1; } else { logevent("Allocated pty"); } + } else { + ssh_editing = ssh_echoing = 1; } /* @@ -2750,7 +2858,11 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) */ ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */ - if (*cfg.remote_cmd) { + if (cfg.ssh_subsys) { + ssh2_pkt_addstring("subsystem"); + ssh2_pkt_addbool(1); /* want reply */ + ssh2_pkt_addstring(cfg.remote_cmd); + } else if (*cfg.remote_cmd) { ssh2_pkt_addstring("exec"); ssh2_pkt_addbool(1); /* want reply */ ssh2_pkt_addstring(cfg.remote_cmd); @@ -2790,8 +2902,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) /* * Transfer data! */ + ldisc_send(NULL, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; - begin_session(); while (1) { static int try_send; crReturnV; @@ -2908,10 +3020,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) c = smalloc(sizeof(struct ssh_channel)); if (typelen == 3 && !memcmp(type, "x11", 3)) { - char *rh; if (!ssh_X11_fwd_enabled) error = "X11 forwarding is not enabled"; - else if ( x11_init(&c->u.x11.s, cfg.x11_display, c, &rh) != NULL ) { + else if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) { error = "Unable to open an X11 connection"; } else { c->type = CHAN_X11; @@ -3003,6 +3114,8 @@ static char *ssh_init (char *host, int port, char **realhost) { #endif ssh_send_ok = 0; + ssh_editing = 0; + ssh_echoing = 0; p = connect_to_host(host, port, realhost); if (p != NULL) @@ -3098,6 +3211,12 @@ static Socket ssh_socket(void) { return s; } static int ssh_sendok(void) { return ssh_send_ok; } +static int ssh_ldisc(int option) { + if (option == LD_ECHO) return ssh_echoing; + if (option == LD_EDIT) return ssh_editing; + return FALSE; +} + Backend ssh_backend = { ssh_init, ssh_send, @@ -3105,5 +3224,6 @@ Backend ssh_backend = { ssh_special, ssh_socket, ssh_sendok, + ssh_ldisc, 22 };