X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/7cca0d811c4d1b5bb346cd60fdfa461a558aefec..dacbd0e88298088e0506eb653ae3d1f596085f67:/ssh.c diff --git a/ssh.c b/ssh.c index 7511e085..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" @@ -16,7 +17,7 @@ #endif #define logevent(s) { logevent(s); \ - if (IS_SCP && (scp_flags & SCP_VERBOSE) != 0) \ + if (!(flags & FLAG_CONNECTION) && (flags & FLAG_VERBOSE)) \ fprintf(stderr, "%s\n", s); } #define SSH1_MSG_DISCONNECT 1 @@ -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) { } @@ -160,10 +181,41 @@ static struct ssh_compress *cscomp = NULL; static struct ssh_compress *sccomp = NULL; static struct ssh_kex *kex = NULL; static struct ssh_hostkey *hostkey = NULL; -int scp_flags = 0; 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, @@ -177,11 +229,9 @@ static int size_needed = FALSE; static void s_write (char *buf, int len) { while (len > 0) { int i = send (s, buf, len, 0); - if (IS_SCP) { - noise_ultralight(i); - if (i <= 0) - fatalbox("Lost connection while sending"); - } + noise_ultralight(i); + if (i <= 0) + fatalbox("Lost connection while sending"); if (i > 0) len -= i, buf += i; } @@ -191,8 +241,7 @@ static int s_read (char *buf, int len) { int ret = 0; while (len > 0) { int i = recv (s, buf, len, 0); - if (IS_SCP) - noise_ultralight(i); + noise_ultralight(i); if (i > 0) len -= i, buf += i, ret += i; else @@ -202,10 +251,11 @@ static int s_read (char *buf, int len) { } static void c_write (char *buf, int len) { - if (IS_SCP) { - if (len > 0 && buf[len-1] == '\n') len--; - if (len > 0 && buf[len-1] == '\r') len--; - if (len > 0) { fwrite(buf, len, 1, stderr); fputc('\n', stderr); } + if (!(flags & FLAG_CONNECTION)) { + int i; + for (i = 0; i < len; i++) + if (buf[i] != '\r') + fputc(buf[i], stderr); return; } while (len--) @@ -224,6 +274,7 @@ struct Packet { static struct Packet pktin = { 0, 0, NULL, NULL, 0 }; static struct Packet pktout = { 0, 0, NULL, NULL, 0 }; +static int ssh_version; static void (*ssh_protocol)(unsigned char *in, int inlen, int ispkt); static void ssh1_protocol(unsigned char *in, int inlen, int ispkt); static void ssh2_protocol(unsigned char *in, int inlen, int ispkt); @@ -309,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"); @@ -518,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); @@ -544,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); @@ -586,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; } } @@ -731,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); } @@ -958,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. */ @@ -975,6 +1011,7 @@ static int do_ssh_init(void) { logevent("Using SSH protocol version 2"); s_write(vstring, strlen(vstring)); ssh_protocol = ssh2_protocol; + ssh_version = 2; s_rdpkt = ssh2_rdpkt; } else { /* @@ -988,8 +1025,10 @@ static int do_ssh_init(void) { logevent("Using SSH protocol version 1"); s_write(vstring, strlen(vstring)); ssh_protocol = ssh1_protocol; + 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 (!IS_SCP && !*cfg.username) { + if ((flags & FLAG_CONNECTION) && !*cfg.username) { c_write("login as: ", 10); while (pos >= 0) { crWaitUntil(!ispkt); @@ -1173,9 +1212,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) char stuff[200]; strncpy(username, cfg.username, 99); username[99] = '\0'; - if (!IS_SCP) { + if (flags & FLAG_VERBOSE) { sprintf(stuff, "Sent username \"%s\".\r\n", username); - c_write(stuff, strlen(stuff)); + c_write(stuff, strlen(stuff)); } } @@ -1198,13 +1237,106 @@ 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; - if (IS_SCP) { + if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD && !FLAG_WINDOWED) { char prompt[200]; sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost); if (!ssh_get_password(prompt, password, sizeof(password))) { @@ -1239,12 +1371,34 @@ 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) { @@ -1741,11 +2004,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) } /* + * SSH2: remote identifier for the main session channel. + */ +static unsigned long ssh_remote_channel; + +/* * 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; @@ -1775,7 +2042,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int pos = 0; static char c; - if (!IS_SCP && !*cfg.username) { + if ((flags & FLAG_CONNECTION) && !*cfg.username) { c_write("login as: ", 10); while (pos >= 0) { crWaitUntilV(!ispkt); @@ -1815,13 +2082,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) char stuff[200]; strncpy(username, cfg.username, 99); username[99] = '\0'; - if (!IS_SCP) { + if (flags & FLAG_VERBOSE) { sprintf(stuff, "Using username \"%s\".\r\n", username); c_write(stuff, strlen(stuff)); } } - if (IS_SCP) { + if (!(flags & FLAG_WINDOWED)) { char prompt[200]; sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost); if (!ssh_get_password(prompt, password, sizeof(password))) { @@ -1906,7 +2173,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) if (ssh2_pkt_getuint32() != 100) { fatalbox("Server's channel confirmation cited wrong channel"); } - their_channel = ssh2_pkt_getuint32(); + ssh_remote_channel = ssh2_pkt_getuint32(); remote_winsize = ssh2_pkt_getuint32(); remote_maxpkt = ssh2_pkt_getuint32(); logevent("Opened channel for session"); @@ -1915,7 +2182,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * Now allocate a pty for the session. */ ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(their_channel); /* recipient channel */ + ssh2_pkt_adduint32(ssh_remote_channel); /* recipient channel */ ssh2_pkt_addstring("pty-req"); ssh2_pkt_addbool(1); /* want reply */ ssh2_pkt_addstring(cfg.termtype); @@ -1944,7 +2211,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) * Start a shell. */ ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(their_channel); /* recipient channel */ + ssh2_pkt_adduint32(ssh_remote_channel); /* recipient channel */ ssh2_pkt_addstring("shell"); ssh2_pkt_addbool(1); /* want reply */ ssh2_pkt_send(); @@ -1963,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) { @@ -1991,7 +2259,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } else { /* FIXME: for now, ignore window size */ ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); - ssh2_pkt_adduint32(their_channel); + ssh2_pkt_adduint32(ssh_remote_channel); ssh2_pkt_addstring_start(); ssh2_pkt_addstring_data(in, inlen); ssh2_pkt_send(); @@ -2033,7 +2301,7 @@ static char *ssh_init (HWND hwnd, char *host, int port, char **realhost) { if (!do_ssh_init()) return "Protocol initialisation error"; - if (WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR) + if (hwnd && WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR) switch (WSAGetLastError()) { case WSAENETDOWN: return "Network is down"; default: return "WSAAsyncSelect(): unknown error"; @@ -2117,10 +2385,22 @@ static void ssh_size(void) { } /* - * (Send Telnet special codes) + * Send Telnet special codes. TS_EOF is useful for `plink', so you + * can send an EOF and collect resulting output (e.g. `plink + * hostname sort'). */ static void ssh_special (Telnet_Special code) { - /* do nothing */ + if (code == TS_EOF) { + if (ssh_version = 1) { + send_packet(SSH1_CMSG_EOF, PKT_END); + } else { + ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + ssh2_pkt_adduint32(ssh_remote_channel); + ssh2_pkt_send(); + } + } else { + /* do nothing */ + } } @@ -2134,8 +2414,6 @@ static void get_packet(void) long to_read; int len; - assert(IS_SCP); - p = NULL; len = 0; @@ -2164,8 +2442,6 @@ int ssh_scp_recv(unsigned char *buf, int len) static unsigned char *pending_input_ptr; int to_read = len; - assert(IS_SCP); - if (pending_input_len >= to_read) { memcpy(buf, pending_input_ptr, to_read); pending_input_ptr += to_read; @@ -2229,7 +2505,6 @@ int ssh_scp_recv(unsigned char *buf, int len) */ void ssh_scp_send(unsigned char *buf, int len) { - assert(IS_SCP); if (s == INVALID_SOCKET) return; send_packet(SSH1_CMSG_STDIN_DATA, @@ -2242,7 +2517,6 @@ void ssh_scp_send(unsigned char *buf, int len) */ void ssh_scp_send_eof(void) { - assert(IS_SCP); if (s == INVALID_SOCKET) return; send_packet(SSH1_CMSG_EOF, PKT_END); @@ -2258,8 +2532,6 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost) { char buf[160], *p; - assert(IS_SCP); - #ifdef MSCRYPTOAPI if (crypto_startup() == 0) return "Microsoft high encryption pack not installed!"; @@ -2295,11 +2567,16 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost) return NULL; } +static SOCKET ssh_socket(void) { return s; } + +static int ssh_sendok(void) { return ssh_send_ok; } Backend ssh_backend = { ssh_init, ssh_msg, ssh_send, ssh_size, - ssh_special + ssh_special, + ssh_socket, + ssh_sendok };