X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/033b4cef1e52d65786b00df47f57361b96ca4321..7cca0d811c4d1b5bb346cd60fdfa461a558aefec:/ssh.c diff --git a/ssh.c b/ssh.c index d327471c..7511e085 100644 --- a/ssh.c +++ b/ssh.c @@ -23,6 +23,9 @@ #define SSH1_SMSG_PUBLIC_KEY 2 #define SSH1_CMSG_SESSION_KEY 3 #define SSH1_CMSG_USER 4 +#define SSH1_CMSG_AUTH_RSA 6 +#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 +#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 #define SSH1_CMSG_AUTH_PASSWORD 9 #define SSH1_CMSG_REQUEST_PTY 10 #define SSH1_CMSG_WINDOW_SIZE 11 @@ -54,6 +57,32 @@ #define SSH2_MSG_NEWKEYS 21 #define SSH2_MSG_KEXDH_INIT 30 #define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 +#define SSH2_EXTENDED_DATA_STDERR 1 #define GET_32BIT(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 24) | \ @@ -67,7 +96,7 @@ (cp)[2] = (unsigned char)((value) >> 8); \ (cp)[3] = (unsigned char)(value); } -enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR }; +enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM }; /* Coroutine mechanics for the sillier bits of the code */ #define crBegin1 static int crLine = 0; @@ -86,6 +115,7 @@ enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR }; #define crStop(z) do{ crLine = 0; return (z); }while(0) #define crStopV do{ crLine = 0; return; }while(0) #define crWaitUntil(c) do { crReturn(0); } while (!(c)) +#define crWaitUntilV(c) do { crReturnV; } while (!(c)) extern struct ssh_cipher ssh_3des; extern struct ssh_cipher ssh_3des_ssh2; @@ -347,16 +377,8 @@ next_packet: /* FIXME */ } #endif - debug(("Got initblk:")); - for (i = 0; i < cipherblk; i++) - debug((" %02x", (unsigned char)pktin.data[i])); - debug(("\r\n")); if (sccipher) sccipher->decrypt(pktin.data, cipherblk); - debug(("Decrypted initblk:")); - for (i = 0; i < cipherblk; i++) - debug((" %02x", (unsigned char)pktin.data[i])); - debug(("\r\n")); /* * Now get the length and padding figures. @@ -401,10 +423,12 @@ next_packet: if (sccipher) sccipher->decrypt(pktin.data + cipherblk, packetlen - cipherblk); +#if 0 debug(("Got packet len=%d pad=%d\r\n", len, pad)); for (i = 0; i < packetlen; i++) debug((" %02x", (unsigned char)pktin.data[i])); debug(("\r\n")); +#endif /* * Check the MAC. @@ -416,9 +440,8 @@ next_packet: pktin.savedpos = 6; pktin.type = pktin.data[5]; - /* - * FIXME: handle IGNORE and DEBUG messages. - */ + if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG) + goto next_packet; /* FIXME: print DEBUG message */ crFinish(0); } @@ -494,6 +517,8 @@ static void send_packet(int pkttype, ...) unsigned char *p, *argp, argchar; unsigned long argint; int pktlen, argtype, arglen; + Bignum bn; + int i; pktlen = 0; va_start(args, pkttype); @@ -517,6 +542,13 @@ static void send_packet(int pkttype, ...) arglen = strlen(argp); pktlen += 4 + arglen; break; + case PKT_BIGNUM: + bn = va_arg(args, Bignum); + i = 16 * bn[0] - 1; + while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 ) + i--; + pktlen += 2 + (i+7)/8; + break; default: assert(0); } @@ -552,6 +584,21 @@ static void send_packet(int pkttype, ...) memcpy(p + 4, argp, arglen); p += 4 + arglen; break; + case PKT_BIGNUM: + bn = va_arg(args, Bignum); + i = 16 * bn[0] - 1; + while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 ) + i--; + *p++ = (i >> 8) & 0xFF; + *p++ = i & 0xFF; + i = (i + 7) / 8; + while (i-- > 0) { + if (i % 2) + *p++ = bn[i/2+1] >> 8; + else + *p++ = bn[i/2+1] & 0xFF; + } + break; } } va_end(args); @@ -694,6 +741,178 @@ fflush(fp); SHA_Bytes(s, str, len); } +/* + * SSH2 packet construction functions. + */ +void ssh2_pkt_adddata(void *data, int len) { + pktout.length += len; + if (pktout.maxlen < pktout.length) { + pktout.maxlen = pktout.length + 256; + pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) : + realloc(pktout.data, pktout.maxlen+APIEXTRA)); + if (!pktout.data) + fatalbox("Out of memory"); + } + memcpy(pktout.data+pktout.length-len, data, len); +} +void ssh2_pkt_addbyte(unsigned char byte) { + ssh2_pkt_adddata(&byte, 1); +} +void ssh2_pkt_init(int pkt_type) { + pktout.length = 5; + ssh2_pkt_addbyte((unsigned char)pkt_type); +} +void ssh2_pkt_addbool(unsigned char value) { + ssh2_pkt_adddata(&value, 1); +} +void ssh2_pkt_adduint32(unsigned long value) { + unsigned char x[4]; + PUT_32BIT(x, value); + ssh2_pkt_adddata(x, 4); +} +void ssh2_pkt_addstring_start(void) { + ssh2_pkt_adduint32(0); + pktout.savedpos = pktout.length; +} +void ssh2_pkt_addstring_str(char *data) { + ssh2_pkt_adddata(data, strlen(data)); + PUT_32BIT(pktout.data + pktout.savedpos - 4, + pktout.length - pktout.savedpos); +} +void ssh2_pkt_addstring_data(char *data, int len) { + ssh2_pkt_adddata(data, len); + PUT_32BIT(pktout.data + pktout.savedpos - 4, + pktout.length - pktout.savedpos); +} +void ssh2_pkt_addstring(char *data) { + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_str(data); +} +char *ssh2_mpint_fmt(Bignum b, int *len) { + unsigned char *p; + int i, n = b[0]; + p = malloc(n * 2 + 1); + if (!p) + fatalbox("out of memory"); + p[0] = 0; + for (i = 0; i < n; i++) { + p[i*2+1] = (b[n-i] >> 8) & 0xFF; + p[i*2+2] = (b[n-i] ) & 0xFF; + } + i = 0; + while (p[i] == 0 && (p[i+1] & 0x80) == 0) + i++; + memmove(p, p+i, n*2+1-i); + *len = n*2+1-i; + return p; +} +void ssh2_pkt_addmp(Bignum b) { + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data(p, len); + free(p); +} +void ssh2_pkt_send(void) { + int cipherblk, maclen, padding, i; + static unsigned long outgoing_sequence = 0; + + /* + * Add padding. At least four bytes, and must also bring total + * length (minus MAC) up to a multiple of the block size. + */ + cipherblk = cipher ? cipher->blksize : 8; /* block size */ + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + padding = 4; + padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk; + pktout.data[4] = padding; + for (i = 0; i < padding; i++) + pktout.data[pktout.length + i] = random_byte(); + PUT_32BIT(pktout.data, pktout.length + padding - 4); + if (csmac) + csmac->generate(pktout.data, pktout.length + padding, + outgoing_sequence); + outgoing_sequence++; /* whether or not we MACed */ + +#if 0 + debug(("Sending packet len=%d\r\n", pktout.length+padding)); + for (i = 0; i < pktout.length+padding; i++) + debug((" %02x", (unsigned char)pktout.data[i])); + debug(("\r\n")); +#endif + + if (cscipher) + cscipher->encrypt(pktout.data, pktout.length + padding); + maclen = csmac ? csmac->len : 0; + + s_write(pktout.data, pktout.length + padding + maclen); +} + +#if 0 +void bndebug(char *string, Bignum b) { + unsigned char *p; + int i, len; + p = ssh2_mpint_fmt(b, &len); + debug(("%s", string)); + for (i = 0; i < len; i++) + debug((" %02x", p[i])); + debug(("\r\n")); + free(p); +} +#endif + +void sha_mpint(SHA_State *s, Bignum b) { + unsigned char *p; + int len; + p = ssh2_mpint_fmt(b, &len); + sha_string(s, p, len); + free(p); +} + +/* + * SSH2 packet decode functions. + */ +unsigned long ssh2_pkt_getuint32(void) { + unsigned long value; + if (pktin.length - pktin.savedpos < 4) + return 0; /* arrgh, no way to decline (FIXME?) */ + value = GET_32BIT(pktin.data+pktin.savedpos); + pktin.savedpos += 4; + return value; +} +void ssh2_pkt_getstring(char **p, int *length) { + *p = NULL; + if (pktin.length - pktin.savedpos < 4) + return; + *length = GET_32BIT(pktin.data+pktin.savedpos); + pktin.savedpos += 4; + if (pktin.length - pktin.savedpos < *length) + return; + *p = pktin.data+pktin.savedpos; + pktin.savedpos += *length; +} +Bignum ssh2_pkt_getmp(void) { + char *p; + int i, j, length; + Bignum b; + + ssh2_pkt_getstring(&p, &length); + if (!p) + return NULL; + if (p[0] & 0x80) + fatalbox("internal error: Can't handle negative mpints"); + b = newbn((length+1)/2); + for (i = 0; i < length; i++) { + j = length - 1 - i; + if (j & 1) + b[j/2+1] |= ((unsigned char)p[i]) << 8; + else + b[j/2+1] |= ((unsigned char)p[i]); + } + return b; +} + static int do_ssh_init(void) { char c, *vsp; char version[10]; @@ -739,7 +958,7 @@ static int do_ssh_init(void) { vlog[strcspn(vlog, "\r\n")] = '\0'; logevent(vlog); - if (ssh_versioncmp(version, "1.99") >= 0) { + if (ssh_versioncmp(version, "2.0" /* FIXME: "1.99" */ ) >= 0) { /* * This is a v2 server. Begin v2 protocol. */ @@ -780,12 +999,13 @@ static int do_ssh_init(void) { static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) { int i, j, len; - unsigned char session_id[16]; unsigned char *rsabuf, *keystr1, *keystr2; unsigned char cookie[8]; struct RSAKey servkey, hostkey; struct MD5Context md5c; static unsigned long supported_ciphers_mask, supported_auths_mask; + static int tried_publickey; + static unsigned char session_id[16]; int cipher_type; crBegin; @@ -799,8 +1019,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) memcpy(cookie, pktin.body, 8); - i = makekey(pktin.body+8, &servkey, &keystr1); - j = makekey(pktin.body+8+i, &hostkey, &keystr2); + i = makekey(pktin.body+8, &servkey, &keystr1, 0); + j = makekey(pktin.body+8+i, &hostkey, &keystr2, 0); /* * Hash the host key and print the hash in the log box. Just as @@ -969,17 +1189,20 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crWaitUntil(ispkt); + tried_publickey = 0; + while (pktin.type == SSH1_SMSG_FAILURE) { static char password[100]; static int pos; static char c; static int pwpkt_type; - /* * Show password prompt, having first obtained it via a TIS * exchange if we're doing TIS authentication. */ pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + if (*cfg.keyfile && !tried_publickey) + pwpkt_type = SSH1_CMSG_AUTH_RSA; if (IS_SCP) { char prompt[200]; @@ -997,59 +1220,133 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) } } else { - if (pktin.type == SSH1_SMSG_FAILURE && - cfg.try_tis_auth && - (supported_auths_mask & (1<= 0) { - crWaitUntil(!ispkt); - while (inlen--) switch (c = *in++) { - case 10: case 13: - password[pos] = 0; - pos = -1; - break; - case 8: case 127: - if (pos > 0) - pos--; - break; - case 21: case 27: - pos = 0; - break; - case 3: case 4: - random_save_seed(); - exit(0); - break; - default: - if (((c >= ' ' && c <= '~') || - ((unsigned char)c >= 160)) && pos < 40) - password[pos++] = c; - break; - } - } - c_write("\r\n", 2); + pos = 0; + while (pos >= 0) { + crWaitUntil(!ispkt); + while (inlen--) switch (c = *in++) { + case 10: case 13: + password[pos] = 0; + pos = -1; + break; + case 8: case 127: + if (pos > 0) + pos--; + break; + case 21: case 27: + pos = 0; + break; + case 3: case 4: + random_save_seed(); + exit(0); + break; + default: + if (((c >= ' ' && c <= '~') || + ((unsigned char)c >= 160)) && pos < sizeof(password)) + password[pos++] = c; + break; + } + } + c_write("\r\n", 2); } - send_packet(pwpkt_type, PKT_STR, password, PKT_END); + tryauth: + if (pwpkt_type == SSH1_CMSG_AUTH_RSA) { + /* + * Try public key authentication with the specified + * key file. + */ + static struct RSAKey pubkey; + static Bignum challenge, response; + static int i; + static unsigned char buffer[32]; + + tried_publickey = 1; + i = loadrsakey(cfg.keyfile, &pubkey, password); + if (i == 0) { + c_write("Couldn't load public key from ", 30); + c_write(cfg.keyfile, strlen(cfg.keyfile)); + c_write(".\r\n", 3); + continue; /* go and try password */ + } + if (i == -1) { + c_write("Wrong passphrase.\r\n", 19); + tried_publickey = 0; + continue; /* try again */ + } + + /* + * Send a public key attempt. + */ + send_packet(SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, pubkey.modulus, PKT_END); + + crWaitUntil(ispkt); + if (pktin.type == SSH1_SMSG_FAILURE) { + c_write("Server refused our public key.\r\n", 32); + continue; /* go and try password */ + } + if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) + fatalbox("Bizarre response to offer of public key"); + ssh1_read_bignum(pktin.body, &challenge); + response = rsadecrypt(challenge, &pubkey); + freebn(pubkey.private_exponent); /* burn the evidence */ + + for (i = 0; i < 32; i += 2) { + buffer[i] = response[16-i/2] >> 8; + buffer[i+1] = response[16-i/2] & 0xFF; + } + + MD5Init(&md5c); + MD5Update(&md5c, buffer, 32); + MD5Update(&md5c, session_id, 16); + MD5Final(buffer, &md5c); + + send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, buffer, 16, PKT_END); + + crWaitUntil(ispkt); + if (pktin.type == SSH1_SMSG_FAILURE) { + c_write("Failed to authenticate with our public key.\r\n", 45); + continue; /* go and try password */ + } else if (pktin.type != SSH1_SMSG_SUCCESS) { + fatalbox("Bizarre response to RSA authentication response"); + } + + break; /* we're through! */ + } else { + send_packet(pwpkt_type, PKT_STR, password, PKT_END); + } logevent("Sent password"); memset(password, 0, strlen(password)); crWaitUntil(ispkt); @@ -1135,165 +1432,6 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { } /* - * SSH2 packet construction functions. - */ -void ssh2_pkt_adddata(void *data, int len) { - pktout.length += len; - if (pktout.maxlen < pktout.length) { - pktout.maxlen = pktout.length + 256; - pktout.data = (pktout.data == NULL ? malloc(pktout.maxlen+APIEXTRA) : - realloc(pktout.data, pktout.maxlen+APIEXTRA)); - if (!pktout.data) - fatalbox("Out of memory"); - } - memcpy(pktout.data+pktout.length-len, data, len); -} -void ssh2_pkt_addbyte(unsigned char byte) { - ssh2_pkt_adddata(&byte, 1); -} -void ssh2_pkt_init(int pkt_type) { - pktout.length = 5; - ssh2_pkt_addbyte((unsigned char)pkt_type); -} -void ssh2_pkt_addbool(unsigned char value) { - ssh2_pkt_adddata(&value, 1); -} -void ssh2_pkt_adduint32(unsigned long value) { - unsigned char x[4]; - PUT_32BIT(x, value); - ssh2_pkt_adddata(x, 4); -} -void ssh2_pkt_addstring_start(void) { - ssh2_pkt_adduint32(0); - pktout.savedpos = pktout.length; -} -void ssh2_pkt_addstring_str(char *data) { - ssh2_pkt_adddata(data, strlen(data)); - PUT_32BIT(pktout.data + pktout.savedpos - 4, - pktout.length - pktout.savedpos); -} -void ssh2_pkt_addstring_data(char *data, int len) { - ssh2_pkt_adddata(data, len); - PUT_32BIT(pktout.data + pktout.savedpos - 4, - pktout.length - pktout.savedpos); -} -void ssh2_pkt_addstring(char *data) { - ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_str(data); -} -char *ssh2_mpint_fmt(Bignum b, int *len) { - unsigned char *p; - int i, n = b[0]; - p = malloc(n * 2 + 1); - if (!p) - fatalbox("out of memory"); - p[0] = 0; - for (i = 0; i < n; i++) { - p[i*2+1] = (b[n-i] >> 8) & 0xFF; - p[i*2+2] = (b[n-i] ) & 0xFF; - } - i = 0; - while (p[i] == 0 && (p[i+1] & 0x80) == 0) - i++; - memmove(p, p+i, n*2+1-i); - *len = n*2+1-i; - return p; -} -void ssh2_pkt_addmp(Bignum b) { - unsigned char *p; - int len; - p = ssh2_mpint_fmt(b, &len); - ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_data(p, len); - free(p); -} -void ssh2_pkt_send(void) { - int cipherblk, maclen, padding, i; - static unsigned long outgoing_sequence = 0; - - /* - * Add padding. At least four bytes, and must also bring total - * length (minus MAC) up to a multiple of the block size. - */ - cipherblk = cipher ? cipher->blksize : 8; /* block size */ - cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ - padding = 4; - padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk; - pktout.data[4] = padding; - for (i = 0; i < padding; i++) - pktout.data[pktout.length + i] = random_byte(); - PUT_32BIT(pktout.data, pktout.length + padding - 4); - if (csmac) - csmac->generate(pktout.data, pktout.length + padding, - outgoing_sequence); - outgoing_sequence++; /* whether or not we MACed */ - if (cscipher) - cscipher->encrypt(pktout.data, pktout.length + padding); - maclen = csmac ? csmac->len : 0; -#if 0 - debug(("Sending packet len=%d\r\n", pktout.length+padding+maclen)); - for (i = 0; i < pktout.length+padding+maclen; i++) - debug((" %02x", (unsigned char)pktout.data[i])); - debug(("\r\n")); -#endif - s_write(pktout.data, pktout.length + padding + maclen); -} - -void sha_mpint(SHA_State *s, Bignum b) { - unsigned char *p; - int len; - p = ssh2_mpint_fmt(b, &len); - sha_string(s, p, len); - free(p); -} - -/* - * SSH2 packet decode functions. - */ -void ssh2_pkt_getstring(char **p, int *length) { - *p = NULL; - if (pktin.length - pktin.savedpos < 4) - return; - *length = GET_32BIT(pktin.data+pktin.savedpos); - pktin.savedpos += 4; - if (pktin.length - pktin.savedpos < *length) - return; - *p = pktin.data+pktin.savedpos; - pktin.savedpos += *length; -} -Bignum ssh2_pkt_getmp(void) { - char *p; - int i, j, length; - Bignum b; - - ssh2_pkt_getstring(&p, &length); - if (!p) - return NULL; - if (p[0] & 0x80) - fatalbox("internal error: Can't handle negative mpints"); - b = newbn((length+1)/2); - for (i = 0; i < length; i++) { - j = length - 1 - i; - if (j & 1) - b[j/2+1] |= ((unsigned char)p[i]) << 8; - else - b[j/2+1] |= ((unsigned char)p[i]); - } - return b; -} - -void bndebug(char *string, Bignum b) { - unsigned char *p; - int i, len; - p = ssh2_mpint_fmt(b, &len); - debug(("%s", string)); - for (i = 0; i < len; i++) - debug((" %02x", p[i])); - debug(("\r\n")); - free(p); -} - -/* * Utility routine for decoding comma-separated strings in KEXINIT. */ int in_commasep_string(char *needle, char *haystack, int haylen) { @@ -1341,9 +1479,9 @@ void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) { } /* - * Handle the SSH2 key exchange phase. + * Handle the SSH2 transport layer. */ -static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) +static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) { static int i, len; static char *str; @@ -1354,13 +1492,15 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) static struct ssh_mac *scmac_tobe = NULL; static struct ssh_compress *cscomp_tobe = NULL; static struct ssh_compress *sccomp_tobe = NULL; - static char *hostkeydata, *sigdata; + static char *hostkeydata, *sigdata, *keystr; static int hostkeylen, siglen; static unsigned char exchange_hash[20]; static unsigned char keyspace[40]; crBegin; + random_init(); + begin_key_exchange: /* * Construct and send our key exchange packet. */ @@ -1441,8 +1581,9 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) * Now examine the other side's KEXINIT to see what we're up * to. */ - if (pktin.type != SSH2_MSG_KEXINIT) + if (pktin.type != SSH2_MSG_KEXINIT) { fatalbox("expected key exchange packet from server"); + } kex = NULL; hostkey = NULL; cscipher_tobe = NULL; sccipher_tobe = NULL; csmac_tobe = NULL; scmac_tobe = NULL; cscomp_tobe = NULL; sccomp_tobe = NULL; pktin.savedpos += 16; /* skip garbage cookie */ @@ -1502,14 +1643,6 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) break; } } - debug(("key exchange is %s\r\n", kex ? kex->name : NULL)); - debug(("host key alg is %s\r\n", hostkey ? hostkey->name : NULL)); - debug(("cscipher alg is %s\r\n", cscipher_tobe ? cscipher_tobe->name : NULL)); - debug(("sccipher alg is %s\r\n", sccipher_tobe ? sccipher_tobe->name : NULL)); - debug(("csmac alg is %s\r\n", csmac_tobe ? csmac_tobe->name : NULL)); - debug(("scmac alg is %s\r\n", scmac_tobe ? scmac_tobe->name : NULL)); - debug(("cscomp alg is %s\r\n", cscomp_tobe ? cscomp_tobe->name : NULL)); - debug(("sccomp alg is %s\r\n", sccomp_tobe ? sccomp_tobe->name : NULL)); /* * Currently we only support Diffie-Hellman and DSS, so let's @@ -1522,21 +1655,19 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) * Now we begin the fun. Generate and send e for Diffie-Hellman. */ e = dh_create_e(); - bndebug("e=", e); ssh2_pkt_init(SSH2_MSG_KEXDH_INIT); ssh2_pkt_addmp(e); ssh2_pkt_send(); crWaitUntil(ispkt); - if (pktin.type != SSH2_MSG_KEXDH_REPLY) + if (pktin.type != SSH2_MSG_KEXDH_REPLY) { fatalbox("expected key exchange packet from server"); + } ssh2_pkt_getstring(&hostkeydata, &hostkeylen); f = ssh2_pkt_getmp(); - bndebug("f=", f); ssh2_pkt_getstring(&sigdata, &siglen); K = dh_find_K(f); - bndebug("K=", K); sha_string(&exhash, hostkeydata, hostkeylen); sha_mpint(&exhash, e); @@ -1544,29 +1675,37 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) sha_mpint(&exhash, K); SHA_Final(&exhash, exchange_hash); +#if 0 debug(("Exchange hash is:\r\n")); for (i = 0; i < 20; i++) debug((" %02x", exchange_hash[i])); debug(("\r\n")); +#endif + + hostkey->setkey(hostkeydata, hostkeylen); + if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20)) + fatalbox("Server failed host key check"); /* - * FIXME: verify host key. This bit will be moderately - * unpleasant, because of having to rewrite it to work - * alongside the old scheme. + * Expect SSH2_MSG_NEWKEYS from server. */ + crWaitUntil(ispkt); + if (pktin.type != SSH2_MSG_NEWKEYS) + fatalbox("expected new-keys packet from server"); /* - * FIXME: verify signature of exchange hash. + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) */ + keystr = hostkey->fmtkey(); + verify_ssh_host_key(savedhost, keystr); + free(keystr); /* - * Send SSH2_MSG_NEWKEYS. Expect it from server. + * Send SSH2_MSG_NEWKEYS. */ ssh2_pkt_init(SSH2_MSG_NEWKEYS); ssh2_pkt_send(); - crWaitUntil(ispkt); - if (pktin.type != SSH2_MSG_NEWKEYS) - fatalbox("expected new-keys packet from server"); /* * Create and initialise session keys. @@ -1588,31 +1727,291 @@ static int do_ssh2_kex(unsigned char *in, int inlen, int ispkt) ssh2_mkkey(K, exchange_hash, 'F', keyspace); scmac->setsckey(keyspace); /* - * Now we're encrypting. Send a test packet (FIXME). + * Now we're encrypting. Begin returning 1 to the protocol main + * function so that other things can run on top of the + * transport. If we ever see a KEXINIT, we must go back to the + * start. */ - crWaitUntil(!ispkt); - ssh2_pkt_init(SSH2_MSG_IGNORE); - ssh2_pkt_addstring("oo-er"); - ssh2_pkt_send(); - - crWaitUntil(0); + do { + crReturn(1); + } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT)); + goto begin_key_exchange; crFinish(1); } -static void ssh2_protocol(unsigned char *in, int inlen, int ispkt) { +/* + * Handle the SSH2 userauth and connection layers. + */ +static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) +{ + static unsigned long their_channel; + static unsigned long remote_winsize; + static unsigned long remote_maxpkt; + crBegin; - random_init(); + /* + * Request userauth protocol, and await a response to it. + */ + ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + ssh2_pkt_addstring("ssh-userauth"); + ssh2_pkt_send(); + crWaitUntilV(ispkt); + if (pktin.type != SSH2_MSG_SERVICE_ACCEPT) + fatalbox("Server refused user authentication protocol"); + + /* + * FIXME: currently we support only password authentication. + * (This places us technically in violation of the SSH2 spec. + * We must fix this.) + */ + while (1) { + /* + * Get a username and a password. + */ + static char username[100]; + static char password[100]; + static int pos = 0; + static char c; - while (!do_ssh2_kex(in, inlen, ispkt)) { + if (!IS_SCP && !*cfg.username) { + c_write("login as: ", 10); + while (pos >= 0) { + crWaitUntilV(!ispkt); + while (inlen--) switch (c = *in++) { + case 10: case 13: + username[pos] = 0; + pos = -1; + break; + case 8: case 127: + if (pos > 0) { + c_write("\b \b", 3); + pos--; + } + break; + case 21: case 27: + while (pos > 0) { + c_write("\b \b", 3); + pos--; + } + break; + case 3: case 4: + random_save_seed(); + exit(0); + break; + default: + if (((c >= ' ' && c <= '~') || + ((unsigned char)c >= 160)) && pos < 40) { + username[pos++] = c; + c_write(&c, 1); + } + break; + } + } + c_write("\r\n", 2); + username[strcspn(username, "\n\r")] = '\0'; + } else { + char stuff[200]; + strncpy(username, cfg.username, 99); + username[99] = '\0'; + if (!IS_SCP) { + sprintf(stuff, "Using username \"%s\".\r\n", username); + c_write(stuff, strlen(stuff)); + } + } + + if (IS_SCP) { + char prompt[200]; + sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost); + if (!ssh_get_password(prompt, password, sizeof(password))) { + /* + * get_password failed to get a password (for + * example because one was supplied on the command + * line which has already failed to work). + * Terminate. + */ + logevent("No more passwords to try"); + ssh_state = SSH_STATE_CLOSED; + crReturnV; + } + } else { + c_write("password: ", 10); + + pos = 0; + while (pos >= 0) { + crWaitUntilV(!ispkt); + while (inlen--) switch (c = *in++) { + case 10: case 13: + password[pos] = 0; + pos = -1; + break; + case 8: case 127: + if (pos > 0) + pos--; + break; + case 21: case 27: + pos = 0; + break; + case 3: case 4: + random_save_seed(); + exit(0); + break; + default: + if (((c >= ' ' && c <= '~') || + ((unsigned char)c >= 160)) && pos < 40) + password[pos++] = c; + break; + } + } + c_write("\r\n", 2); + } + + 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(); + + crWaitUntilV(ispkt); + if (pktin.type != SSH2_MSG_USERAUTH_SUCCESS) { + c_write("Access denied\r\n", 15); + logevent("Authentication refused"); + } else + break; + } + + /* + * Now we're authenticated for the connection protocol. The + * connection protocol will automatically have started at this + * point; there's no need to send SERVICE_REQUEST. + */ + + /* + * So now create a channel with a session in it. + */ + ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring("session"); + ssh2_pkt_adduint32(100); /* as good as any */ + ssh2_pkt_adduint32(0xFFFFFFFFUL); /* very big window which we ignore */ + ssh2_pkt_adduint32(0xFFFFFFFFUL); /* very big max pkt size */ + ssh2_pkt_send(); + crWaitUntilV(ispkt); + if (pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + fatalbox("Server refused to open a session"); + /* FIXME: error data comes back in FAILURE packet */ + } + if (ssh2_pkt_getuint32() != 100) { + fatalbox("Server's channel confirmation cited wrong channel"); + } + their_channel = ssh2_pkt_getuint32(); + remote_winsize = ssh2_pkt_getuint32(); + remote_maxpkt = ssh2_pkt_getuint32(); + logevent("Opened channel for session"); + + /* + * Now allocate a pty for the session. + */ + ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(their_channel); /* recipient channel */ + ssh2_pkt_addstring("pty-req"); + ssh2_pkt_addbool(1); /* want reply */ + ssh2_pkt_addstring(cfg.termtype); + ssh2_pkt_adduint32(cols); + ssh2_pkt_adduint32(rows); + ssh2_pkt_adduint32(0); /* pixel width */ + ssh2_pkt_adduint32(0); /* pixel height */ + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data("\0", 1); /* TTY_OP_END, no special options */ + ssh2_pkt_send(); + + do { /* FIXME: pay attention to these */ + crWaitUntilV(ispkt); + } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + + if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { + fatalbox("Server got confused by pty request"); + } + c_write("Server refused to allocate pty\r\n", 32); + } else { + logevent("Allocated pty"); + } + + /* + * Start a shell. + */ + ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(their_channel); /* recipient channel */ + ssh2_pkt_addstring("shell"); + ssh2_pkt_addbool(1); /* want reply */ + ssh2_pkt_send(); + do { /* FIXME: pay attention to these */ + crWaitUntilV(ispkt); + } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { + if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { + fatalbox("Server got confused by shell request"); + } + fatalbox("Server refused to start a shell"); + } else { + logevent("Started a shell"); + } + + /* + * Transfer data! + */ + while (1) { crReturnV; + if (ispkt) { + if (pktin.type == SSH2_MSG_CHANNEL_DATA || + pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) { + char *data; + int length; + if (ssh2_pkt_getuint32() != 100) + continue; /* wrong channel */ + if (pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA && + ssh2_pkt_getuint32() != SSH2_EXTENDED_DATA_STDERR) + continue; /* extended but not stderr */ + ssh2_pkt_getstring(&data, &length); + if (data) + c_write(data, length); + } else if (pktin.type == SSH2_MSG_DISCONNECT) { + ssh_state = SSH_STATE_CLOSED; + logevent("Received disconnect request"); + } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) { + continue; /* exit status et al; ignore (FIXME?) */ + } else if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { + continue; /* ignore for now (FIXME!) */ + } else { + fatalbox("Strange packet received: type %d", pktin.type); + } + } else { + /* FIXME: for now, ignore window size */ + ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); + ssh2_pkt_adduint32(their_channel); + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data(in, inlen); + ssh2_pkt_send(); + } } crFinishV; } /* + * Handle the top-level SSH2 protocol. + */ +static void ssh2_protocol(unsigned char *in, int inlen, int ispkt) +{ + if (do_ssh2_transport(in, inlen, ispkt) == 0) + return; + do_ssh2_authconn(in, inlen, ispkt); +} + +/* * Called to set up the connection. Will arrange for WM_NETEVENT * messages to be passed to the specified window, whose window * procedure should then call telnet_msg(). @@ -1904,4 +2303,3 @@ Backend ssh_backend = { ssh_size, ssh_special }; -