X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/edd0cb8aef57080ae884e06731a7892ca8cdba44..fae1a71b5a2fd1b2983be523350c3dae4b5e6903:/ssh.c diff --git a/ssh.c b/ssh.c index 45ecf260..64a069ef 100644 --- a/ssh.c +++ b/ssh.c @@ -1,7 +1,13 @@ +/* + * SSH backend. + */ + #include #include #include #include +#include +#include #include "putty.h" #include "tree234.h" @@ -77,6 +83,9 @@ #define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ #define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ #define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ +#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ +#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ +#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ #define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ #define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ #define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ @@ -106,6 +115,7 @@ */ #define SSH2_PKTCTX_DHGROUP 0x0001 #define SSH2_PKTCTX_DHGEX 0x0002 +#define SSH2_PKTCTX_RSAKEX 0x0004 #define SSH2_PKTCTX_KEX_MASK 0x000F #define SSH2_PKTCTX_PUBLICKEY 0x0010 #define SSH2_PKTCTX_PASSWORD 0x0020 @@ -333,6 +343,9 @@ static char *ssh2_pkt_type(int pkt_ctx, int type) 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); + translatec(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); + translatec(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); + translatec(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); translate(SSH2_MSG_USERAUTH_REQUEST); translate(SSH2_MSG_USERAUTH_FAILURE); translate(SSH2_MSG_USERAUTH_SUCCESS); @@ -708,7 +721,7 @@ struct ssh_tag { void *cs_comp_ctx, *sc_comp_ctx; const struct ssh_kex *kex; const struct ssh_signkey *hostkey; - unsigned char v2_session_id[32]; + unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; int v2_session_id_len; void *kex_ctx; @@ -724,6 +737,7 @@ struct ssh_tag { tree234 *channels; /* indexed by local id */ struct ssh_channel *mainchan; /* primary session channel */ + int ncmode; /* is primary channel direct-tcpip? */ int exitcode; int close_expected; int clean_exit; @@ -1046,7 +1060,7 @@ static void c_write_stderr(int trusted, const char *buf, int len) { int i; for (i = 0; i < len; i++) - if (buf[i] != '\r' && (trusted || buf[i] & 0x60)) + if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60))) fputc(buf[i], stderr); } @@ -1436,11 +1450,17 @@ static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p) return biglen + 4; /* len(length+padding+type+data+CRC) */ } +static int s_write(Ssh ssh, void *data, int len) +{ + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, 0, NULL); + return sk_write(ssh->s, (char *)data, len); +} + static void s_wrpkt(Ssh ssh, struct Packet *pkt) { int len, backlog, offset; len = s_wrpkt_prepare(ssh, pkt, &offset); - backlog = sk_write(ssh->s, (char *)pkt->data + offset, len); + backlog = s_write(ssh, pkt->data + offset, len); if (backlog > SSH_MAX_BACKLOG) ssh_throttle_all(ssh, 1, backlog); ssh_free_packet(pkt); @@ -1477,6 +1497,7 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap) while ((argtype = va_arg(ap, int)) != PKT_END) { unsigned char *argp, argchar; + char *sargp; unsigned long argint; int arglen; switch (argtype) { @@ -1495,8 +1516,8 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap) ssh_pkt_adddata(pkt, argp, arglen); break; case PKT_STR: - argp = va_arg(ap, unsigned char *); - ssh_pkt_addstring(pkt, argp); + sargp = va_arg(ap, char *); + ssh_pkt_addstring(pkt, sargp); break; case PKT_BIGNUM: bn = va_arg(ap, Bignum); @@ -1642,7 +1663,7 @@ static void ssh_pkt_addstring(struct Packet *pkt, char *data) static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b) { int len = ssh1_bignum_length(b); - unsigned char *data = snewn(len, char); + unsigned char *data = snewn(len, unsigned char); (void) ssh1_write_bignum(data, b); ssh_pkt_adddata(pkt, data, len); sfree(data); @@ -1824,7 +1845,7 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) return; } len = ssh2_pkt_construct(ssh, pkt); - backlog = sk_write(ssh->s, (char *)pkt->data, len); + backlog = s_write(ssh, pkt->data, len); if (backlog > SSH_MAX_BACKLOG) ssh_throttle_all(ssh, 1, backlog); @@ -1850,6 +1871,7 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore) * get encrypted with a known IV. */ struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(ipkt); ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE); } len = ssh2_pkt_construct(ssh, pkt); @@ -1892,7 +1914,6 @@ static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) ssh2_pkt_send_noqueue(ssh, pkt); } -#if 0 /* disused */ /* * Either queue or defer a packet, depending on whether queueing is * set. @@ -1904,7 +1925,6 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) else ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); } -#endif /* * Send the whole deferred data block constructed by @@ -1922,8 +1942,7 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) static void ssh_pkt_defersend(Ssh ssh) { int backlog; - backlog = sk_write(ssh->s, (char *)ssh->deferred_send_data, - ssh->deferred_len); + backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len); ssh->deferred_len = ssh->deferred_size = 0; sfree(ssh->deferred_send_data); ssh->deferred_send_data = NULL; @@ -1939,6 +1958,74 @@ static void ssh_pkt_defersend(Ssh ssh) } /* + * Send a packet whose length needs to be disguised (typically + * passwords or keyboard-interactive responses). + */ +static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt, + int padsize) +{ +#if 0 + if (0) { + /* + * The simplest way to do this is to adjust the + * variable-length padding field in the outgoing packet. + * + * Currently compiled out, because some Cisco SSH servers + * don't like excessively padded packets (bah, why's it + * always Cisco?) + */ + pkt->forcepad = padsize; + ssh2_pkt_send(ssh, pkt); + } else +#endif + { + /* + * If we can't do that, however, an alternative approach is + * to use the pkt_defer mechanism to bundle the packet + * tightly together with an SSH_MSG_IGNORE such that their + * combined length is a constant. So first we construct the + * final form of this packet and defer its sending. + */ + ssh2_pkt_defer(ssh, pkt); + + /* + * Now construct an SSH_MSG_IGNORE which includes a string + * that's an exact multiple of the cipher block size. (If + * the cipher is NULL so that the block size is + * unavailable, we don't do this trick at all, because we + * gain nothing by it.) + */ + if (ssh->cscipher) { + int stringlen, i; + + stringlen = (256 - ssh->deferred_len); + stringlen += ssh->cscipher->blksize - 1; + stringlen -= (stringlen % ssh->cscipher->blksize); + if (ssh->cscomp) { + /* + * Temporarily disable actual compression, so we + * can guarantee to get this string exactly the + * length we want it. The compression-disabling + * routine should return an integer indicating how + * many bytes we should adjust our string length + * by. + */ + stringlen -= + ssh->cscomp->disable_compression(ssh->cs_comp_ctx); + } + pkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + ssh2_pkt_addstring_start(pkt); + for (i = 0; i < stringlen; i++) { + char c = (char) random_byte(); + ssh2_pkt_addstring_data(pkt, &c, 1); + } + ssh2_pkt_defer(ssh, pkt); + } + ssh_pkt_defersend(ssh); + } +} + +/* * Send all queued SSH-2 packets. We send them by means of * ssh2_pkt_defer_noqueue(), in case they included a pair of * packets that needed to be lumped together. @@ -2155,6 +2242,13 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) ssh->remote_bugs = 0; + /* + * General notes on server version strings: + * - Not all servers reporting "Cisco-1.25" have all the bugs listed + * here -- in particular, we've heard of one that's perfectly happy + * with SSH1_MSG_IGNOREs -- but this string never seems to change, + * so we can't distinguish them. + */ if (ssh->cfg.sshbug_ignore1 == FORCE_ON || (ssh->cfg.sshbug_ignore1 == AUTO && (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || @@ -2412,7 +2506,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c) } logeventf(ssh, "We claim version: %.*s", strcspn(verstring, "\015\012"), verstring); - sk_write(ssh->s, verstring, strlen(verstring)); + s_write(ssh, verstring, strlen(verstring)); sfree(verstring); if (ssh->version == 2) do_ssh2_transport(ssh, NULL, -1, NULL); @@ -2432,7 +2526,9 @@ static int do_ssh_init(Ssh ssh, unsigned char c) static void ssh_process_incoming_data(Ssh ssh, unsigned char **data, int *datalen) { - struct Packet *pktin = ssh->s_rdpkt(ssh, data, datalen); + struct Packet *pktin; + + pktin = ssh->s_rdpkt(ssh, data, datalen); if (pktin) { ssh->protocol(ssh, NULL, 0, pktin); ssh_free_packet(pktin); @@ -2475,6 +2571,9 @@ static void ssh_set_frozen(Ssh ssh, int frozen) static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) { + /* Log raw data, if we're in that mode. */ + log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, 0, NULL); + crBegin(ssh->ssh_gotdata_crstate); /* @@ -2611,6 +2710,9 @@ static int ssh_closing(Plug plug, const char *error_msg, int error_code, error_msg = "Server closed network connection"; } + if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0) + ssh->exitcode = 0; + if (need_notify) notify_remote_exit(ssh->frontend); @@ -3222,7 +3324,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, while (pktin->type == SSH1_SMSG_FAILURE) { s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - if (agent_exists() && !s->tried_agent) { + if (ssh->cfg.tryagent && agent_exists() && !s->tried_agent) { /* * Attempt RSA authentication using Pageant. */ @@ -3256,13 +3358,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->p += 4; logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - if (s->publickey_blob && - !memcmp(s->p, s->publickey_blob, - s->publickey_bloblen)) { - logevent("This key matches configured key file"); - s->tried_publickey = 1; - } + unsigned char *pkblob = s->p; s->p += 4; { int n, ok = FALSE; @@ -3295,6 +3391,17 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, break; } } + if (s->publickey_blob) { + if (!memcmp(pkblob, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", s->keyi); + s->tried_publickey = 1; + } else + /* Skip non-configured key */ + continue; + } + logeventf(ssh, "Trying Pageant key #%d", s->keyi); send_packet(ssh, SSH1_CMSG_AUTH_RSA, PKT_BIGNUM, s->key.modulus, PKT_END); crWaitUntil(pktin); @@ -3385,6 +3492,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, break; } sfree(s->response); + if (s->publickey_blob && !s->tried_publickey) + logevent("Configured key file not in Pageant"); } if (s->authed) break; @@ -3459,9 +3568,11 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, break; /* go and try something else */ } else if (ret == -1) { c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ - s->tried_publickey = 0; got_passphrase = FALSE; /* and try again */ + } else { + assert(0 && "unexpected return from loadrsakey()"); + got_passphrase = FALSE; /* placate optimisers */ } } @@ -3476,7 +3587,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, crWaitUntil(pktin); if (pktin->type == SSH1_SMSG_FAILURE) { c_write_str(ssh, "Server refused our public key.\r\n"); - continue; /* go and try password */ + continue; /* go and try something else */ } if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { bombout(("Bizarre response to offer of public key")); @@ -3516,7 +3627,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (flags & FLAG_VERBOSE) c_write_str(ssh, "Failed to authenticate with" " our public key.\r\n"); - continue; /* go and try password */ + continue; /* go and try something else */ } else if (pktin->type != SSH1_SMSG_SUCCESS) { bombout(("Bizarre response to RSA authentication response")); crStop(0); @@ -3679,19 +3790,19 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, * magnitude of the password length, but it will * introduce a bit of extra uncertainty. * - * A few servers (the old 1.2.18 through 1.2.22) - * can't deal with SSH1_MSG_IGNORE. For these - * servers, we need an alternative defence. We make - * use of the fact that the password is interpreted - * as a C string: so we can append a NUL, then some - * random data. + * A few servers can't deal with SSH1_MSG_IGNORE, at + * least in this context. For these servers, we need + * an alternative defence. We make use of the fact + * that the password is interpreted as a C string: + * so we can append a NUL, then some random data. * - * One server (a Cisco one) can deal with neither - * SSH1_MSG_IGNORE _nor_ a padded password string. - * For this server we are left with no defences + * A few servers can deal with neither SSH1_MSG_IGNORE + * here _nor_ a padded password string. + * For these servers we are left with no defences * against password length sniffing. */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { /* * The server can deal with SSH1_MSG_IGNORE, so * we can use the primary defence. @@ -3760,10 +3871,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, PKTT_OTHER, PKT_END); } else { /* - * The server has _both_ - * BUG_CHOKES_ON_SSH1_IGNORE and - * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is - * therefore nothing we can do. + * The server is believed unable to cope with + * any of our password camouflage methods. */ int len; len = strlen(s->cur_prompt->prompts[0]->result); @@ -4545,7 +4654,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) /* Data for an agent message. Buffer it. */ while (len > 0) { if (c->u.a.lensofar < 4) { - unsigned int l = min(4 - c->u.a.lensofar, len); + unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len); memcpy(c->u.a.msglen + c->u.a.lensofar, p, l); p += l; @@ -4562,7 +4671,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) if (c->u.a.lensofar >= 4 && len > 0) { unsigned int l = min(c->u.a.totallen - c->u.a.lensofar, - len); + (unsigned)len); memcpy(c->u.a.message + c->u.a.lensofar, p, l); p += l; @@ -4956,7 +5065,10 @@ static int first_in_commasep_string(char *needle, char *haystack, int haylen) /* * SSH-2 key creation method. + * (Currently assumes 2 lots of any hash are sufficient to generate + * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.) */ +#define SSH2_MKKEY_ITERS (2) static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr, unsigned char *keyspace) { @@ -5000,10 +5112,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, const struct ssh_mac *scmac_tobe; const struct ssh_compress *cscomp_tobe; const struct ssh_compress *sccomp_tobe; - char *hostkeydata, *sigdata, *keystr, *fingerprint; - int hostkeylen, siglen; + char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; + int hostkeylen, siglen, rsakeylen; void *hkey; /* actual host key */ - unsigned char exchange_hash[32]; + void *rsakey; /* for RSA kex */ + unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; int n_preferred_kex; const struct ssh_kexes *preferred_kex[KEX_MAX]; int n_preferred_ciphers; @@ -5056,7 +5169,11 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, s->preferred_kex[s->n_preferred_kex++] = &ssh_diffiehellman_group1; break; - case CIPHER_WARN: + case KEX_RSA: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_rsa_kex; + break; + case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ if (i < KEX_MAX - 1) { @@ -5455,6 +5572,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, crWaitUntil(pktin); /* Ignore packet */ } + if (ssh->kex->main_type == KEXTYPE_DH) { + /* XXX The lines below should be reindented before this is committed.*/ /* * Work out the number of bits of key we will need from the key * exchange. We start with the maximum key length of either @@ -5511,7 +5630,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh->kex->groupname); } - logevent("Doing Diffie-Hellman key exchange"); + logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s", + ssh->kex->hash->text_name); /* * Now generate and send e for Diffie-Hellman. */ @@ -5529,6 +5649,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); if (!s->f) { bombout(("unable to parse key exchange reply packet")); @@ -5550,11 +5671,120 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, } hash_mpint(ssh->kex->hash, ssh->exhash, s->e); hash_mpint(ssh->kex->hash, ssh->exhash, s->f); + + dh_cleanup(ssh->kex_ctx); + freebn(s->f); + if (!ssh->kex->pdata) { + freebn(s->g); + freebn(s->p); + } + /* XXX end incorrectly-indented section */ + } else { + logeventf(ssh, "Doing RSA key exchange with hash %s", + ssh->kex->hash->text_name); + ssh->pkt_ctx |= SSH2_PKTCTX_RSAKEX; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + bombout(("expected RSA public key packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + hash_string(ssh->kex->hash, ssh->exhash, + s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + + { + char *keydata; + ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); + s->rsakeydata = snewn(s->rsakeylen, char); + memcpy(s->rsakeydata, keydata, s->rsakeylen); + } + + s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); + if (!s->rsakey) { + sfree(s->rsakeydata); + bombout(("unable to parse RSA public key from server")); + crStop(0); + } + + hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); + + /* + * Next, set up a shared secret K, of precisely KLEN - + * 2*HLEN - 49 bits, where KLEN is the bit length of the + * RSA key modulus and HLEN is the bit length of the hash + * we're using. + */ + { + int klen = ssh_rsakex_klen(s->rsakey); + int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); + int i, byte = 0; + unsigned char *kstr1, *kstr2, *outstr; + int kstr1len, kstr2len, outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + kstr1 = ssh2_mpint_fmt(s->K, &kstr1len); + kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char); + PUT_32BIT(kstr2, kstr1len); + memcpy(kstr2 + 4, kstr1, kstr1len); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len, + outstr, outstrlen, s->rsakey); + + /* + * And send it off in a return packet. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, outstr, outstrlen); + ssh2_pkt_send_noqueue(ssh, s->pktout); + + hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen); + + sfree(kstr2); + sfree(kstr1); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsakey); + + crWaitUntil(pktin); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + sfree(s->rsakeydata); + bombout(("expected signature packet from server")); + crStop(0); + } + + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + + sfree(s->rsakeydata); + } + hash_mpint(ssh->kex->hash, ssh->exhash, s->K); assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); ssh->kex->hash->final(ssh->exhash, s->exchange_hash); - dh_cleanup(ssh->kex_ctx); ssh->kex_ctx = NULL; #if 0 @@ -5562,7 +5792,6 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, dmemdump(s->exchange_hash, ssh->kex->hash->hlen); #endif - s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); if (!s->hkey || !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, (char *)s->exchange_hash, @@ -5653,13 +5882,21 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * hash from the _first_ key exchange. */ { - unsigned char keyspace[40]; + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace); + assert((ssh->cscipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace); + assert(ssh->cscipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace); + assert(ssh->csmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace); + memset(keyspace, 0, sizeof(keyspace)); } logeventf(ssh, "Initialised %.200s client->server encryption", @@ -5711,13 +5948,21 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, * hash from the _first_ key exchange. */ { - unsigned char keyspace[40]; + unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS]; + assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace); + assert((ssh->sccipher->keylen+7) / 8 <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace); + assert(ssh->sccipher->blksize <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace); ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace); + assert(ssh->scmac->len <= + ssh->kex->hash->hlen * SSH2_MKKEY_ITERS); ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace); + memset(keyspace, 0, sizeof(keyspace)); } logeventf(ssh, "Initialised %.200s server->client encryption", ssh->sccipher->text_name); @@ -5728,14 +5973,9 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh->sccomp->text_name); /* - * Free key exchange data. + * Free shared secret. */ - freebn(s->f); freebn(s->K); - if (!ssh->kex->pdata) { - freebn(s->g); - freebn(s->p); - } /* * Key exchange is over. Loop straight back round if we have a @@ -5962,7 +6202,8 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) case CHAN_AGENT: while (length > 0) { if (c->u.a.lensofar < 4) { - unsigned int l = min(4 - c->u.a.lensofar, length); + unsigned int l = min(4 - c->u.a.lensofar, + (unsigned)length); memcpy(c->u.a.msglen + c->u.a.lensofar, data, l); data += l; @@ -5979,7 +6220,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) if (c->u.a.lensofar >= 4 && length > 0) { unsigned int l = min(c->u.a.totallen - c->u.a.lensofar, - length); + (unsigned)length); memcpy(c->u.a.message + c->u.a.lensofar, data, l); data += l; @@ -6248,11 +6489,13 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) is_plausible = FALSE; } } + ssh->exitcode = 128; /* means `unknown signal' */ if (is_plausible) { if (is_int) { /* Old non-standard OpenSSH. */ int signum = ssh_pkt_getuint32(pktin); fmt_sig = dupprintf(" %d", signum); + ssh->exitcode = 128 + signum; } else { /* As per the drafts. */ char *sig; @@ -6264,6 +6507,60 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) fmt_sig = dupprintf(" \"%.*s\"", siglen, sig); } + + /* + * Really hideous method of translating the + * signal description back into a locally + * meaningful number. + */ + + if (0) + ; +#define TRANSLATE_SIGNAL(s) \ + else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \ + ssh->exitcode = 128 + SIG ## s +#ifdef SIGABRT + TRANSLATE_SIGNAL(ABRT); +#endif +#ifdef SIGALRM + TRANSLATE_SIGNAL(ALRM); +#endif +#ifdef SIGFPE + TRANSLATE_SIGNAL(FPE); +#endif +#ifdef SIGHUP + TRANSLATE_SIGNAL(HUP); +#endif +#ifdef SIGILL + TRANSLATE_SIGNAL(ILL); +#endif +#ifdef SIGINT + TRANSLATE_SIGNAL(INT); +#endif +#ifdef SIGKILL + TRANSLATE_SIGNAL(KILL); +#endif +#ifdef SIGPIPE + TRANSLATE_SIGNAL(PIPE); +#endif +#ifdef SIGQUIT + TRANSLATE_SIGNAL(QUIT); +#endif +#ifdef SIGSEGV + TRANSLATE_SIGNAL(SEGV); +#endif +#ifdef SIGTERM + TRANSLATE_SIGNAL(TERM); +#endif +#ifdef SIGUSR1 + TRANSLATE_SIGNAL(USR1); +#endif +#ifdef SIGUSR2 + TRANSLATE_SIGNAL(USR2); +#endif +#undef TRANSLATE_SIGNAL + else + ssh->exitcode = 128; } core = ssh2_pkt_getbool(pktin); ssh_pkt_getstring(pktin, &msg, &msglen); @@ -6474,11 +6771,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, { struct do_ssh2_authconn_state { enum { - AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE, - AUTH_PASSWORD, - AUTH_KEYBOARD_INTERACTIVE - } method; - enum { AUTH_TYPE_NONE, AUTH_TYPE_PUBLICKEY, AUTH_TYPE_PUBLICKEY_OFFER_LOUD, @@ -6489,22 +6781,23 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } type; int done_service_req; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; - int tried_pubkey_config, tried_agent; + int tried_pubkey_config, done_agent; int kbd_inter_refused; int we_are_in; prompts_t *cur_prompt; int num_prompts; char username[100]; + char *password; int got_username; void *publickey_blob; int publickey_bloblen; int publickey_encrypted; char *publickey_algorithm; char *publickey_comment; - unsigned char request[5], *response, *p; - int responselen; + unsigned char agent_request[5], *agent_response, *agentp; + int agent_responselen; + unsigned char *pkblob_in_agent; int keyi, nkeys; - int authed; char *pkblob, *alg, *commentp; int pklen, alglen, commentlen; int siglen, retlen, len; @@ -6546,6 +6839,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } } + /* Arrange to be able to deal with any BANNERs that come in. + * (We do this now as packets may come in during the next bit.) */ + bufchain_init(&ssh->banner); + ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = + ssh2_msg_userauth_banner; + /* * Misc one-time setup for authentication. */ @@ -6596,6 +6895,68 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } } + /* + * Find out about any keys Pageant has (but if there's a + * public key configured, filter out all others). + */ + s->nkeys = 0; + s->agent_response = NULL; + s->pkblob_in_agent = NULL; + if (ssh->cfg.tryagent && agent_exists()) { + + void *r; + + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(s->agent_request, 1); + s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; + if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while" + " waiting for agent response")); + crStopV; + } + } while (pktin || inlen > 0); + r = ssh->agent_response; + s->agent_responselen = ssh->agent_response_len; + } + s->agent_response = (unsigned char *) r; + if (s->agent_response && s->agent_responselen >= 5 && + s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { + int keyi; + unsigned char *p; + p = s->agent_response + 5; + s->nkeys = GET_32BIT(p); + p += 4; + logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); + if (s->publickey_blob) { + /* See if configured key is in agent. */ + for (keyi = 0; keyi < s->nkeys; keyi++) { + s->pklen = GET_32BIT(p); + if (s->pklen == s->publickey_bloblen && + !memcmp(p+4, s->publickey_blob, + s->publickey_bloblen)) { + logeventf(ssh, "Pageant key #%d matches " + "configured key file", keyi); + s->keyi = keyi; + s->pkblob_in_agent = p; + break; + } + p += 4 + s->pklen; + p += GET_32BIT(p) + 4; /* comment */ + } + if (!s->pkblob_in_agent) { + logevent("Configured key file not in Pageant"); + s->nkeys = 0; + } + } + } + } + } /* @@ -6624,9 +6985,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ s->username[0] = '\0'; s->got_username = FALSE; - bufchain_init(&ssh->banner); - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = - ssh2_msg_userauth_banner; while (!s->we_are_in) { /* * Get a username. @@ -6692,9 +7050,19 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->we_are_in = FALSE; s->tried_pubkey_config = FALSE; - s->tried_agent = FALSE; s->kbd_inter_refused = FALSE; + /* Reset agent request state. */ + s->done_agent = FALSE; + if (s->agent_response) { + if (s->pkblob_in_agent) { + s->agentp = s->pkblob_in_agent; + } else { + s->agentp = s->agent_response + 5 + 4; + s->keyi = 0; + } + } + while (1) { /* * Wait for the result of the last authentication request. @@ -6731,13 +7099,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, break; } - if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { - /* FIXME: perhaps we should support this? */ - bombout(("PASSWD_CHANGEREQ not yet supported")); - crStopV; - } else if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { - bombout(("Strange packet received during authentication: type %d", - pktin->type)); + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { + bombout(("Strange packet received during authentication: " + "type %d", pktin->type)); crStopV; } @@ -6810,172 +7174,153 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - if (s->can_pubkey && agent_exists() && !s->tried_agent) { + if (s->can_pubkey && !s->done_agent && s->nkeys) { /* - * Attempt public-key authentication using Pageant. + * Attempt public-key authentication using a key from Pageant. */ - void *r; - s->authed = FALSE; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; - s->tried_agent = TRUE; + logeventf(ssh, "Trying Pageant key #%d", s->keyi); + + /* Unpack key from agent response */ + s->pklen = GET_32BIT(s->agentp); + s->agentp += 4; + s->pkblob = (char *)s->agentp; + s->agentp += s->pklen; + s->alglen = GET_32BIT(s->pkblob); + s->alg = s->pkblob + 4; + s->commentlen = GET_32BIT(s->agentp); + s->agentp += 4; + s->commentp = (char *)s->agentp; + s->agentp += s->commentlen; + /* s->agentp now points at next key, if any */ + + /* See if server will accept it */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; - logevent("Pageant is running. Requesting keys."); + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - /* Request the keys held by the agent. */ - PUT_32BIT(s->request, 1); - s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; - if (!agent_query(s->request, 5, &r, &s->responselen, - ssh_agent_callback, ssh)) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for agent response")); - crStopV; - } - } while (pktin || inlen > 0); - r = ssh->agent_response; - s->responselen = ssh->agent_response_len; - } - s->response = (unsigned char *) r; - if (s->response && s->responselen >= 5 && - s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { - s->p = s->response + 5; - s->nkeys = GET_32BIT(s->p); - s->p += 4; - logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - void *vret; + /* Offer of key refused. */ + s->gotit = TRUE; - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - s->pklen = GET_32BIT(s->p); - s->p += 4; - if (s->publickey_blob && - s->pklen == s->publickey_bloblen && - !memcmp(s->p, s->publickey_blob, - s->publickey_bloblen)) { - logevent("This key matches configured key file"); - s->tried_pubkey_config = 1; - } - s->pkblob = (char *)s->p; - s->p += s->pklen; - s->alglen = GET_32BIT(s->pkblob); - s->alg = s->pkblob + 4; - s->commentlen = GET_32BIT(s->p); - s->p += 4; - s->commentp = (char *)s->p; - s->p += s->commentlen; - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ - ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); - ssh2_pkt_send(ssh, s->pktout); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - logevent("Key refused"); - continue; - } + } else { + + void *vret; + + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with " + "public key \""); + c_write(ssh, s->commentp, s->commentlen); + c_write_str(ssh, "\" from agent\r\n"); + } - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with " - "public key \""); - c_write(ssh, s->commentp, s->commentlen); - c_write_str(ssh, "\" from agent\r\n"); - } + /* + * Server is willing to accept the key. + * Construct a SIGN_REQUEST. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ + ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */ + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); - /* - * Server is willing to accept the key. - * Construct a SIGN_REQUEST. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ - ssh2_pkt_addbool(s->pktout, TRUE); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); - - s->siglen = s->pktout->length - 5 + 4 + - ssh->v2_session_id_len; - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) - s->siglen -= 4; - s->len = 1; /* message type */ - s->len += 4 + s->pklen; /* key blob */ - s->len += 4 + s->siglen; /* data to sign */ - s->len += 4; /* flags */ - s->agentreq = snewn(4 + s->len, char); - PUT_32BIT(s->agentreq, s->len); - s->q = s->agentreq + 4; - *s->q++ = SSH2_AGENTC_SIGN_REQUEST; - PUT_32BIT(s->q, s->pklen); - s->q += 4; - memcpy(s->q, s->pkblob, s->pklen); - s->q += s->pklen; - PUT_32BIT(s->q, s->siglen); + /* Ask agent for signature. */ + s->siglen = s->pktout->length - 5 + 4 + + ssh->v2_session_id_len; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + s->siglen -= 4; + s->len = 1; /* message type */ + s->len += 4 + s->pklen; /* key blob */ + s->len += 4 + s->siglen; /* data to sign */ + s->len += 4; /* flags */ + s->agentreq = snewn(4 + s->len, char); + PUT_32BIT(s->agentreq, s->len); + s->q = s->agentreq + 4; + *s->q++ = SSH2_AGENTC_SIGN_REQUEST; + PUT_32BIT(s->q, s->pklen); + s->q += 4; + memcpy(s->q, s->pkblob, s->pklen); + s->q += s->pklen; + PUT_32BIT(s->q, s->siglen); + s->q += 4; + /* Now the data to be signed... */ + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(s->q, ssh->v2_session_id_len); s->q += 4; - /* Now the data to be signed... */ - if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { - PUT_32BIT(s->q, ssh->v2_session_id_len); - s->q += 4; - } - memcpy(s->q, ssh->v2_session_id, - ssh->v2_session_id_len); - s->q += ssh->v2_session_id_len; - memcpy(s->q, s->pktout->data + 5, - s->pktout->length - 5); - s->q += s->pktout->length - 5; - /* And finally the (zero) flags word. */ - PUT_32BIT(s->q, 0); - if (!agent_query(s->agentreq, s->len + 4, - &vret, &s->retlen, - ssh_agent_callback, ssh)) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server" - " while waiting for agent" - " response")); - crStopV; - } - } while (pktin || inlen > 0); - vret = ssh->agent_response; - s->retlen = ssh->agent_response_len; - } - s->ret = vret; - sfree(s->agentreq); - if (s->ret) { - if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) { - logevent("Sending Pageant's response"); - ssh2_add_sigblob(ssh, s->pktout, - s->pkblob, s->pklen, - s->ret + 9, - GET_32BIT(s->ret + 5)); - ssh2_pkt_send(ssh, s->pktout); - s->authed = TRUE; - break; - } else { - logevent - ("Pageant failed to answer challenge"); - sfree(s->ret); + } + memcpy(s->q, ssh->v2_session_id, + ssh->v2_session_id_len); + s->q += ssh->v2_session_id_len; + memcpy(s->q, s->pktout->data + 5, + s->pktout->length - 5); + s->q += s->pktout->length - 5; + /* And finally the (zero) flags word. */ + PUT_32BIT(s->q, 0); + if (!agent_query(s->agentreq, s->len + 4, + &vret, &s->retlen, + ssh_agent_callback, ssh)) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server" + " while waiting for agent" + " response")); + crStopV; } + } while (pktin || inlen > 0); + vret = ssh->agent_response; + s->retlen = ssh->agent_response_len; + } + s->ret = vret; + sfree(s->agentreq); + if (s->ret) { + if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) { + logevent("Sending Pageant's response"); + ssh2_add_sigblob(ssh, s->pktout, + s->pkblob, s->pklen, + s->ret + 9, + GET_32BIT(s->ret + 5)); + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + /* FIXME: less drastic response */ + bombout(("Pageant failed to answer challenge")); + crStopV; } } - if (s->authed) - continue; } - sfree(s->response); + + /* Do we have any keys left to try? */ + if (s->pkblob_in_agent) { + s->done_agent = TRUE; + s->tried_pubkey_config = TRUE; + } else { + s->keyi++; + if (s->keyi >= s->nkeys) + s->done_agent = TRUE; + } } else if (s->can_pubkey && s->publickey_blob && !s->tried_pubkey_config) { @@ -7016,7 +7361,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; continue; /* process this new message */ } - s->method = AUTH_PUBLICKEY_FILE; logevent("Offer of public key accepted"); /* @@ -7161,11 +7505,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Keyboard-interactive authentication. */ - char *name, *inst, *lang; - int name_len, inst_len, lang_len; - int i; - s->method = AUTH_KEYBOARD_INTERACTIVE; s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; @@ -7184,7 +7524,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { /* Server is not willing to do keyboard-interactive - * at all. Give up on it entirely. */ + * at all (or, bizarrely but legally, accepts the + * user without actually issuing any prompts). + * Give up on it entirely. */ s->gotit = TRUE; if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) logevent("Keyboard-interactive authentication refused"); @@ -7194,89 +7536,112 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. + * Loop while the server continues to send INFO_REQUESTs. */ - ssh_pkt_getstring(pktin, &name, &name_len); - ssh_pkt_getstring(pktin, &inst, &inst_len); - ssh_pkt_getstring(pktin, &lang, &lang_len); - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - if (name_len) { - /* FIXME: better prefix to distinguish from - * local prompts? */ - s->cur_prompt->name = dupprintf("SSH server: %.*s", - name_len, name); - s->cur_prompt->name_reqd = TRUE; - } else { - s->cur_prompt->name = dupstr("SSH server authentication"); - s->cur_prompt->name_reqd = FALSE; - } - s->cur_prompt->instruction = - dupprintf("Using keyboard-interactive authentication.%s%.*s", - inst_len ? "\n" : "", inst_len, inst); - s->cur_prompt->instr_reqd = TRUE; + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* - * Get the prompts from the packet. - */ - s->num_prompts = ssh_pkt_getuint32(pktin); - for (i = 0; i < s->num_prompts; i++) { - char *prompt; - int prompt_len; - int echo; - static char noprompt[] = - ": "; + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - echo = ssh2_pkt_getbool(pktin); - if (!prompt_len) { - prompt = noprompt; - prompt_len = lenof(noprompt)-1; + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + ssh_pkt_getstring(pktin, &name, &name_len); + ssh_pkt_getstring(pktin, &inst, &inst_len); + ssh_pkt_getstring(pktin, &lang, &lang_len); + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + if (name_len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", name_len, name); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; } - add_prompt(s->cur_prompt, - dupprintf("%.*s", prompt_len, prompt), - echo, SSH_MAX_PASSWORD_LEN); - } + /* FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; - /* - * Get the user's responses. - */ - if (s->num_prompts) { - int ret; /* not live over crReturn */ - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; + /* + * Get the prompts from the packet. + */ + s->num_prompts = ssh_pkt_getuint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + char *prompt; + int prompt_len; + int echo; + static char noprompt[] = + ": "; + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + echo = ssh2_pkt_getbool(pktin); + if (!prompt_len) { + prompt = noprompt; + prompt_len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, + dupprintf("%.*s", prompt_len, prompt), + echo, SSH_MAX_PASSWORD_LEN); } - if (!ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; + + /* + * Get the user's responses. + */ + if (s->num_prompts) { + int ret; /* not live over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } } + + /* + * Send the responses to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_pkt_adduint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[i]->result); + end_log_omission(ssh, s->pktout); + } + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crWaitUntilV(pktin); + } /* - * Send the responses to the server. + * We should have SUCCESS or FAILURE now. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - s->pktout->forcepad = 256; - ssh2_pkt_adduint32(s->pktout, s->num_prompts); - for (i=0; i < s->num_prompts; i++) { - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[i]->result); - end_log_omission(ssh, s->pktout); - } - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/ + s->gotit = TRUE; } else if (s->can_passwd) { @@ -7284,8 +7649,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * Plain old password authentication. */ int ret; /* not live over crReturn */ + int changereq_first_time; /* not live over crReturn */ - s->method = AUTH_PASSWORD; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD; @@ -7314,6 +7679,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, TRUE); crStopV; } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); /* * Send the password packet. @@ -7327,20 +7698,180 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * people who find out how long their password is! */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - s->pktout->forcepad = 256; ssh2_pkt_addstring(s->pktout, s->username); ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ ssh2_pkt_addstring(s->pktout, "password"); ssh2_pkt_addbool(s->pktout, FALSE); dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); + ssh2_pkt_addstring(s->pktout, s->password); end_log_omission(ssh, s->pktout); - ssh2_pkt_send(ssh, s->pktout); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; /*FIXME?*/ + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crWaitUntilV(pktin); + changereq_first_time = TRUE; + + while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + + /* + * We're being asked for a new password + * (perhaps not for the first time). + * Loop until the server accepts it. + */ + + int got_new = FALSE; /* not live over crReturn */ + char *prompt; /* not live over crReturn */ + int prompt_len; /* not live over crReturn */ + + { + char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + logevent(msg); + c_write_str(ssh, msg); + c_write_str(ssh, "\r\n"); + } + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = + dupprintf("%.*s", prompt_len, prompt); + s->cur_prompt->instr_reqd = TRUE; + /* + * There's no explicit requirement in the protocol + * for the "old" passwords in the original and + * password-change messages to be the same, and + * apparently some Cisco kit supports password change + * by the user entering a blank password originally + * and the real password subsequently, so, + * reluctantly, we prompt for the old password again. + * + * (On the other hand, some servers don't even bother + * to check this field.) + */ + add_prompt(s->cur_prompt, + dupstr("Current password (blank for previously entered password): "), + FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + memset(s->password, 0, strlen(s->password)); + sfree(s->password); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + + /* + * If the user specified a new original password + * (IYSWIM), overwrite any previously specified + * one. + * (A side effect is that the user doesn't have to + * re-enter it if they louse up the new password.) + */ + if (s->cur_prompt->prompts[0]->result[0]) { + memset(s->password, 0, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = + dupstr(s->cur_prompt->prompts[0]->result); + } + + /* + * Check the two new passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[1]->result, + s->cur_prompt->prompts[2]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + c_write_str(ssh, "Passwords do not match\r\n"); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, TRUE); + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, s->password); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[1]->result); + free_prompts(s->cur_prompt); + end_log_omission(ssh, s->pktout); + ssh2_pkt_send_with_padding(ssh, s->pktout, 256); + logevent("Sent new password"); + + /* + * Now see what the server has to say about it. + * (If it's CHANGEREQ again, it's not happy with the + * new password.) + */ + crWaitUntilV(pktin); + changereq_first_time = FALSE; + + } + + /* + * We need to reexamine the current pktin at the top + * of the loop. Either: + * - we weren't asked to change password at all, in + * which case it's a SUCCESS or FAILURE with the + * usual meaning + * - we sent a new password, and the server was + * either OK with it (SUCCESS or FAILURE w/partial + * success) or unhappy with the _old_ password + * (FAILURE w/o partial success) + * In any of these cases, we go back to the top of + * the loop and start again. + */ + s->gotit = TRUE; + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + memset(s->password, 0, strlen(s->password)); + sfree(s->password); } else { @@ -7361,6 +7892,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, sfree(s->publickey_blob); sfree(s->publickey_comment); } + if (s->agent_response) + sfree(s->agent_response); /* * Now the connection protocol has started, one way or another. @@ -7380,7 +7913,58 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Create the main session channel. */ - if (!ssh->cfg.ssh_no_shell) { + if (ssh->cfg.ssh_no_shell) { + ssh->mainchan = NULL; + } else if (*ssh->cfg.ssh_nc_host) { + /* + * Just start a direct-tcpip channel and use it as the main + * channel. + */ + ssh->mainchan = snew(struct ssh_channel); + ssh->mainchan->ssh = ssh; + ssh->mainchan->localid = alloc_channel_id(ssh); + logeventf(ssh, + "Opening direct-tcpip channel to %s:%d in place of session", + ssh->cfg.ssh_nc_host, ssh->cfg.ssh_nc_port); + s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring(s->pktout, "direct-tcpip"); + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid); + ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE; + ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */ + ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */ + ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host); + ssh2_pkt_adduint32(s->pktout, ssh->cfg.ssh_nc_port); + /* + * There's nothing meaningful to put in the originator + * fields, but some servers insist on syntactically correct + * information. + */ + ssh2_pkt_addstring(s->pktout, "0.0.0.0"); + ssh2_pkt_adduint32(s->pktout, 0); + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + bombout(("Server refused to open a direct-tcpip channel")); + crStopV; + /* FIXME: error data comes back in FAILURE packet */ + } + if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { + bombout(("Server's channel confirmation cited wrong channel")); + crStopV; + } + ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); + ssh->mainchan->halfopen = FALSE; + ssh->mainchan->type = CHAN_MAINSESSION; + ssh->mainchan->closes = 0; + ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); + ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); + bufchain_init(&ssh->mainchan->v.v2.outbuffer); + add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); + logevent("Opened direct-tcpip channel"); + ssh->ncmode = TRUE; + } else { ssh->mainchan = snew(struct ssh_channel); ssh->mainchan->ssh = ssh; ssh->mainchan->localid = alloc_channel_id(ssh); @@ -7411,8 +7995,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, add234(ssh->channels, ssh->mainchan); update_specials_menu(ssh->frontend); logevent("Opened channel for session"); - } else - ssh->mainchan = NULL; + ssh->ncmode = FALSE; + } /* * Now we have a channel, make dispatch table entries for @@ -7435,7 +8019,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Potentially enable X11 forwarding. */ - if (ssh->mainchan && ssh->cfg.x11_forward) { + if (ssh->mainchan && !ssh->ncmode && ssh->cfg.x11_forward) { char proto[20], data[64]; logevent("Requesting X11 forwarding"); ssh->x11auth = x11_invent_auth(proto, sizeof(proto), @@ -7483,7 +8067,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Potentially enable agent forwarding. */ - if (ssh->mainchan && ssh->cfg.agentfwd && agent_exists()) { + if (ssh->mainchan && !ssh->ncmode && ssh->cfg.agentfwd && agent_exists()) { logevent("Requesting OpenSSH-style agent forwarding"); s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); @@ -7509,7 +8093,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Now allocate a pty for the session. */ - if (ssh->mainchan && !ssh->cfg.nopty) { + if (ssh->mainchan && !ssh->ncmode && !ssh->cfg.nopty) { /* Unpick the terminal-speed string. */ /* XXX perhaps we should allow no speeds to be sent. */ ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ @@ -7559,7 +8143,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * Simplest thing here is to send all the requests at once, and * then wait for a whole bunch of successes or failures. */ - if (ssh->mainchan && *ssh->cfg.environmt) { + if (ssh->mainchan && !ssh->ncmode && *ssh->cfg.environmt) { char *e = ssh->cfg.environmt; char *var, *varend, *val; @@ -7624,7 +8208,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * this twice if the config data has provided a second choice * of command. */ - if (ssh->mainchan) while (1) { + if (ssh->mainchan && !ssh->ncmode) while (1) { int subsys; char *cmd; @@ -8439,8 +9023,7 @@ static void ssh_unthrottle(void *handle, int bufsize) ssh1_throttle(ssh, -1); } } else { - if (ssh->mainchan && ssh->mainchan->closes == 0) - ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize); + ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize); } } @@ -8473,17 +9056,20 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org) * too much hassle to keep track, and partly I'm not * convinced the server should be told details like that * about my local network configuration. + * The "originator IP address" is syntactically a numeric + * IP address, and some servers (e.g., Tectia) get upset + * if it doesn't match this syntax. */ - ssh2_pkt_addstring(pktout, "client-side-connection"); + ssh2_pkt_addstring(pktout, "0.0.0.0"); ssh2_pkt_adduint32(pktout, 0); ssh2_pkt_send(ssh, pktout); } } -static Socket ssh_socket(void *handle) +static int ssh_connected(void *handle) { Ssh ssh = (Ssh) handle; - return ssh->s; + return ssh->s != NULL; } static int ssh_sendok(void *handle) @@ -8520,7 +9106,7 @@ static int ssh_return_exitcode(void *handle) if (ssh->s != NULL) return -1; else - return (ssh->exitcode >= 0 ? ssh->exitcode : 0); + return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); } /* @@ -8553,7 +9139,7 @@ Backend ssh_backend = { ssh_size, ssh_special, ssh_get_specials, - ssh_socket, + ssh_connected, ssh_return_exitcode, ssh_sendok, ssh_ldisc,