X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/6abbf9e32be79aaaeb5272c95c1325e6f4b4e26d..dacbd0e88298088e0506eb653ae3d1f596085f67:/ssh.c diff --git a/ssh.c b/ssh.c index 7e02bacf..613b23e3 100644 --- a/ssh.c +++ b/ssh.c @@ -5,6 +5,7 @@ #include #include "putty.h" +#include "tree234.h" #include "ssh.h" #include "scp.h" @@ -38,14 +39,34 @@ #define SSH1_SMSG_STDERR_DATA 18 #define SSH1_CMSG_EOF 19 #define SSH1_SMSG_EXIT_STATUS 20 +#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 +#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 +#define SSH1_MSG_CHANNEL_DATA 23 +#define SSH1_MSG_CHANNEL_CLOSE 24 +#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 +#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 +#define SSH1_SMSG_AGENT_OPEN 31 #define SSH1_CMSG_EXIT_CONFIRMATION 33 #define SSH1_MSG_IGNORE 32 #define SSH1_MSG_DEBUG 36 #define SSH1_CMSG_AUTH_TIS 39 #define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 #define SSH1_CMSG_AUTH_TIS_RESPONSE 41 +#define SSH1_CMSG_AUTH_CCARD 70 +#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 +#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 #define SSH1_AUTH_TIS 5 +#define SSH1_AUTH_CCARD 16 + +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 #define SSH2_MSG_DISCONNECT 1 #define SSH2_MSG_IGNORE 2 @@ -133,7 +154,7 @@ struct ssh_hostkey *hostkey_algs[] = { &ssh_dss }; extern struct ssh_mac ssh_sha1; -SHA_State exhash; +static SHA_State exhash; static void nullmac_key(unsigned char *key) { } static void nullmac_generate(unsigned char *blk, int len, unsigned long seq) { } @@ -163,6 +184,38 @@ static struct ssh_hostkey *hostkey = NULL; int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL; static char *savedhost; +static int ssh_send_ok; + +/* + * 2-3-4 tree storing channels. + */ +struct ssh_channel { + int remoteid, localid; + int type; + int closes; + union { + struct ssh_agent_channel { + unsigned char *message; + unsigned char msglen[4]; + int lensofar, totallen; + } a; + } u; +}; +static tree234 *ssh_channels; /* indexed by local id */ +static int ssh_channelcmp(void *av, void *bv) { + struct ssh_channel *a = (struct ssh_channel *)av; + struct ssh_channel *b = (struct ssh_channel *)bv; + if (a->localid < b->localid) return -1; + if (a->localid > b->localid) return +1; + return 0; +} +static int ssh_channelfind(void *av, void *bv) { + int *a = (int *)av; + struct ssh_channel *b = (struct ssh_channel *)bv; + if (*a < b->localid) return -1; + if (*a > b->localid) return +1; + return 0; +} static enum { SSH_STATE_BEFORE_SIZE, @@ -307,7 +360,8 @@ next_packet: if (pktin.type == SSH1_SMSG_STDOUT_DATA || pktin.type == SSH1_SMSG_STDERR_DATA || pktin.type == SSH1_MSG_DEBUG || - pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { + pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE || + pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { long strlen = GET_32BIT(pktin.body); if (strlen + 4 != pktin.length) fatalbox("Received data packet with bogus string length"); @@ -516,7 +570,6 @@ static void send_packet(int pkttype, ...) unsigned long argint; int pktlen, argtype, arglen; Bignum bn; - int i; pktlen = 0; va_start(args, pkttype); @@ -542,10 +595,7 @@ static void send_packet(int pkttype, ...) 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; + pktlen += ssh1_bignum_length(bn); break; default: assert(0); @@ -584,18 +634,7 @@ static void send_packet(int pkttype, ...) 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; - } + p += ssh1_write_bignum(p, bn); break; } } @@ -729,13 +768,8 @@ static int ssh_versioncmp(char *a, char *b) { #include void sha_string(SHA_State *s, void *str, int len) { unsigned char lenblk[4]; -static FILE *fp; PUT_32BIT(lenblk, len); -if (!fp) fp = fopen("h:\\statham\\windows\\putty\\data","wb"); -fwrite(lenblk, 4, 1, fp); SHA_Bytes(s, lenblk, 4); -fwrite(str, len, 1, fp); -fflush(fp); SHA_Bytes(s, str, len); } @@ -956,7 +990,11 @@ static int do_ssh_init(void) { vlog[strcspn(vlog, "\r\n")] = '\0'; logevent(vlog); - if (ssh_versioncmp(version, "2.0" /* FIXME: "1.99" */ ) >= 0) { + /* + * Server version "1.99" means we can choose whether we use v1 + * or v2 protocol. Choice is based on cfg.sshprot. + */ + if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) { /* * This is a v2 server. Begin v2 protocol. */ @@ -990,6 +1028,7 @@ static int do_ssh_init(void) { ssh_version = 1; s_rdpkt = ssh1_rdpkt; } + ssh_send_ok = 0; return 1; } @@ -1133,7 +1172,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static char username[100]; static int pos = 0; static char c; - if (!(flags & FLAG_CONNECTION) && !*cfg.username) { + if ((flags & FLAG_CONNECTION) && !*cfg.username) { c_write("login as: ", 10); while (pos >= 0) { crWaitUntil(!ispkt); @@ -1198,9 +1237,102 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) static int pwpkt_type; /* * Show password prompt, having first obtained it via a TIS - * exchange if we're doing TIS authentication. + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. */ pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + if (agent_exists()) { + /* + * Attempt RSA authentication using Pageant. + */ + static unsigned char request[5], *response, *p; + static int responselen; + static int i, nkeys; + static int authed = FALSE; + void *r; + + logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + PUT_32BIT(request, 1); + request[4] = SSH_AGENTC_REQUEST_RSA_IDENTITIES; + agent_query(request, 5, &r, &responselen); + response = (unsigned char *)r; + if (response) { + p = response + 5; + nkeys = GET_32BIT(p); p += 4; + { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys); + logevent(buf); } + for (i = 0; i < nkeys; i++) { + static struct RSAKey key; + static Bignum challenge; + + { char buf[64]; sprintf(buf, "Trying Pageant key #%d", i); + logevent(buf); } + p += 4; + p += ssh1_read_bignum(p, &key.exponent); + p += ssh1_read_bignum(p, &key.modulus); + send_packet(SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, key.modulus, PKT_END); + crWaitUntil(ispkt); + if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + logevent("Key refused"); + continue; + } + logevent("Received RSA challenge"); + ssh1_read_bignum(pktin.body, &challenge); + { + char *agentreq, *q, *ret; + int len, retlen; + len = 1 + 4; /* message type, bit count */ + len += ssh1_bignum_length(key.exponent); + len += ssh1_bignum_length(key.modulus); + len += ssh1_bignum_length(challenge); + len += 16; /* session id */ + len += 4; /* response format */ + agentreq = malloc(4 + len); + PUT_32BIT(agentreq, len); + q = agentreq + 4; + *q++ = SSH_AGENTC_RSA_CHALLENGE; + PUT_32BIT(q, ssh1_bignum_bitcount(key.modulus)); + q += 4; + q += ssh1_write_bignum(q, key.exponent); + q += ssh1_write_bignum(q, key.modulus); + q += ssh1_write_bignum(q, challenge); + memcpy(q, session_id, 16); q += 16; + PUT_32BIT(q, 1); /* response format */ + agent_query(agentreq, len+4, &ret, &retlen); + free(agentreq); + if (ret) { + if (ret[4] == SSH_AGENT_RSA_RESPONSE) { + logevent("Sending Pageant's response"); + send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, ret+5, 16, PKT_END); + free(ret); + crWaitUntil(ispkt); + if (pktin.type == SSH1_SMSG_SUCCESS) { + logevent("Pageant's response accepted"); + authed = TRUE; + } else + logevent("Pageant's response not accepted"); + } else { + logevent("Pageant failed to answer challenge"); + free(ret); + } + } else { + logevent("No reply received from Pageant"); + } + } + freebn(key.exponent); + freebn(key.modulus); + freebn(challenge); + if (authed) + break; + } + } + if (authed) + break; + } if (*cfg.keyfile && !tried_publickey) pwpkt_type = SSH1_CMSG_AUTH_RSA; @@ -1239,6 +1371,26 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) c_write(pktin.body+4, challengelen); } } + if (pktin.type == SSH1_SMSG_FAILURE && + cfg.try_tis_auth && + (supported_auths_mask & (1<localid > i) + break; /* found a free number */ + i = c->localid + 1; + } + c = malloc(sizeof(struct ssh_channel)); + c->remoteid = GET_32BIT(pktin.body); + c->localid = i; + c->type = SSH1_SMSG_AGENT_OPEN; /* identify channel type */ + add234(ssh_channels, c); + send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, c->localid, + PKT_END); + } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE || + pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) { + /* Remote side closes a channel. */ + int i = GET_32BIT(pktin.body); + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (c) { + int closetype; + closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); + send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END); + c->closes |= closetype; + if (c->closes == 3) { + del234(ssh_channels, c); + free(c); + } + } + } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) { + /* Data sent down one of our channels. */ + int i = GET_32BIT(pktin.body); + int len = GET_32BIT(pktin.body+4); + unsigned char *p = pktin.body+8; + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (c) { + switch(c->type) { + case SSH1_SMSG_AGENT_OPEN: + /* Data for an agent message. Buffer it. */ + while (len > 0) { + if (c->u.a.lensofar < 4) { + int l = min(4 - c->u.a.lensofar, len); + memcpy(c->u.a.msglen + c->u.a.lensofar, p, l); + p += l; len -= l; c->u.a.lensofar += l; + } + if (c->u.a.lensofar == 4) { + c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen); + c->u.a.message = malloc(c->u.a.totallen); + memcpy(c->u.a.message, c->u.a.msglen, 4); + } + if (c->u.a.lensofar >= 4 && len > 0) { + int l = min(c->u.a.totallen - c->u.a.lensofar, len); + memcpy(c->u.a.message + c->u.a.lensofar, p, l); + p += l; len -= l; c->u.a.lensofar += l; + } + if (c->u.a.lensofar == c->u.a.totallen) { + void *reply, *sentreply; + int replylen; + agent_query(c->u.a.message, c->u.a.totallen, + &reply, &replylen); + if (reply) + sentreply = reply; + else { + /* Fake SSH_AGENT_FAILURE. */ + sentreply = "\0\0\0\1\5"; + replylen = 5; + } + send_packet(SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, replylen, + PKT_DATA, sentreply, replylen, + PKT_END); + if (reply) + free(reply); + free(c->u.a.message); + c->u.a.lensofar = 0; + } + } + break; + } + } } else if (pktin.type == SSH1_SMSG_SUCCESS) { /* may be from EXEC_SHELL on some servers */ } else if (pktin.type == SSH1_SMSG_FAILURE) { @@ -1976,6 +2230,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) /* * Transfer data! */ + ssh_send_ok = 1; while (1) { crReturnV; if (ispkt) { @@ -2312,7 +2567,9 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost) return NULL; } -SOCKET ssh_socket(void) { return s; } +static SOCKET ssh_socket(void) { return s; } + +static int ssh_sendok(void) { return ssh_send_ok; } Backend ssh_backend = { ssh_init, @@ -2320,5 +2577,6 @@ Backend ssh_backend = { ssh_send, ssh_size, ssh_special, - ssh_socket + ssh_socket, + ssh_sendok };