X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/f7259d1028b10c11812d455d01ed569a08004268..bc240b210b3bbd3792230e9605414564b0b8e5aa:/ssh.c diff --git a/ssh.c b/ssh.c index a41e3bf4..f6828ddf 100644 --- a/ssh.c +++ b/ssh.c @@ -95,6 +95,8 @@ #define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ #define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ #define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ #define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ #define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ @@ -196,6 +198,12 @@ extern void x11_close(Socket); extern void x11_send(Socket, char *, int); extern void x11_invent_auth(char *, int, char *, int); +extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c); +extern char *pfd_addforward(char *desthost, int destport, int port); +extern void pfd_close(Socket s); +extern void pfd_send(Socket s, char *data, int len); +extern void pfd_confirm(Socket s); + /* * Ciphers for SSH2. We miss out single-DES because it isn't * supported; also 3DES and Blowfish are both done differently from @@ -263,6 +271,8 @@ enum { /* channel types */ CHAN_MAINSESSION, CHAN_X11, CHAN_AGENT, + CHAN_SOCKDATA, + CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */ }; /* @@ -286,9 +296,46 @@ struct ssh_channel { struct ssh_x11_channel { Socket s; } x11; + struct ssh_pfd_channel { + Socket s; + } pfd; } u; }; +/* + * 2-3-4 tree storing remote->local port forwardings. SSH 1 and SSH + * 2 use this structure in different ways, reflecting SSH 2's + * altogether saner approach to port forwarding. + * + * In SSH 1, you arrange a remote forwarding by sending the server + * the remote port number, and the local destination host:port. + * When a connection comes in, the server sends you back that + * host:port pair, and you connect to it. This is a ready-made + * security hole if you're not on the ball: a malicious server + * could send you back _any_ host:port pair, so if you trustingly + * connect to the address it gives you then you've just opened the + * entire inside of your corporate network just by connecting + * through it to a dodgy SSH server. Hence, we must store a list of + * host:port pairs we _are_ trying to forward to, and reject a + * connection request from the server if it's not in the list. + * + * In SSH 2, each side of the connection minds its own business and + * doesn't send unnecessary information to the other. You arrange a + * remote forwarding by sending the server just the remote port + * number. When a connection comes in, the server tells you which + * of its ports was connected to; and _you_ have to remember what + * local host:port pair went with that port number. + * + * Hence: in SSH 1 this structure stores host:port pairs we intend + * to allow connections to, and is indexed by those host:port + * pairs. In SSH 2 it stores a mapping from source port to + * destination host:port pair, and is indexed by source port. + */ +struct ssh_rportfwd { + unsigned sport, dport; + char dhost[256]; +}; + struct Packet { long length; int type; @@ -330,6 +377,8 @@ static int ssh_echoing, ssh_editing; static tree234 *ssh_channels; /* indexed by local id */ static struct ssh_channel *mainchan; /* primary session channel */ +static tree234 *ssh_rportfwds; + static enum { SSH_STATE_PREPACKET, SSH_STATE_BEFORE_SIZE, @@ -393,6 +442,32 @@ static int ssh_channelfind(void *av, void *bv) return 0; } +static int ssh_rportcmp_ssh1(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->dhost, b->dhost)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + return 0; +} + +static int ssh_rportcmp_ssh2(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + return 0; +} + static int alloc_channel_id(void) { const unsigned CHANNEL_NUMBER_OFFSET = 256; @@ -2263,7 +2338,10 @@ void sshfwd_close(struct ssh_channel *c) c->closes = 1; if (c->type == CHAN_X11) { c->u.x11.s = NULL; - logevent("X11 connection terminated"); + logevent("Forwarded X11 connection terminated"); + } else if (c->type == CHAN_SOCKDATA) { + c->u.pfd.s = NULL; + logevent("Forwarded port closed"); } } } @@ -2337,6 +2415,69 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) } } + { + char type, *e; + int n; + int sport,dport; + char sports[256], dports[256], host[256]; + char buf[1024]; + + ssh_rportfwds = newtree234(ssh_rportcmp_ssh1); + /* Add port forwardings. */ + e = cfg.portfwd; + while (*e) { + type = *e++; + n = 0; + while (*e && *e != '\t') + sports[n++] = *e++; + sports[n] = 0; + if (*e == '\t') + e++; + n = 0; + while (*e && *e != ':') + host[n++] = *e++; + host[n] = 0; + if (*e == ':') + e++; + n = 0; + while (*e) + dports[n++] = *e++; + dports[n] = 0; + e++; + dport = atoi(dports); + sport = atoi(sports); + if (sport && dport) { + if (type == 'L') { + pfd_addforward(host, dport, sport); + sprintf(buf, "Local port %d forwarding to %s:%d", + sport, host, dport); + logevent(buf); + } else { + struct ssh_rportfwd *pf; + pf = smalloc(sizeof(*pf)); + strcpy(pf->dhost, host); + pf->dport = dport; + if (add234(ssh_rportfwds, pf) != pf) { + sprintf(buf, + "Duplicate remote port forwarding to %s:%s", + host, dport); + logevent(buf); + sfree(pf); + } else { + sprintf(buf, "Requesting remote port %d forward to %s:%d", + sport, host, dport); + logevent(buf); + send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST, + PKT_INT, sport, + PKT_STR, host, + PKT_INT, dport, + PKT_END); + } + } + } + } + } + if (!cfg.nopty) { send_packet(SSH1_CMSG_REQUEST_PTY, PKT_STR, cfg.termtype, @@ -2460,6 +2601,72 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) PKT_INT, c->remoteid, PKT_INT, c->localid, PKT_END); } + } else if (pktin.type == SSH1_MSG_PORT_OPEN) { + /* Remote side is trying to open a channel to talk to a + * forwarded port. Give them back a local channel number. */ + struct ssh_channel *c; + struct ssh_rportfwd pf; + int hostsize, port; + char host[256], buf[1024]; + char *p, *h, *e; + c = smalloc(sizeof(struct ssh_channel)); + + hostsize = GET_32BIT(pktin.body+4); + for(h = host, p = pktin.body+8; hostsize != 0; hostsize--) { + if (h+1 < host+sizeof(host)) + *h++ = *p; + *p++; + } + *h = 0; + port = GET_32BIT(p); + + strcpy(pf.dhost, host); + pf.dport = port; + + if (find234(ssh_rportfwds, &pf, NULL) == NULL) { + sprintf(buf, "Rejected remote port open request for %s:%d", + host, port); + logevent(buf); + send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, GET_32BIT(pktin.body), PKT_END); + } else { + sprintf(buf, "Received remote port open request for %s:%d", + host, port); + logevent(buf); + e = pfd_newconnect(&c->u.pfd.s, host, port, c); + if (e != NULL) { + char buf[256]; + sprintf(buf, "Port open failed: %s", e); + logevent(buf); + sfree(c); + send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, GET_32BIT(pktin.body), + PKT_END); + } else { + c->remoteid = GET_32BIT(pktin.body); + c->localid = alloc_channel_id(); + c->closes = 0; + c->type = CHAN_SOCKDATA; /* identify channel type */ + add234(ssh_channels, c); + send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, + PKT_INT, c->remoteid, PKT_INT, + c->localid, PKT_END); + logevent("Forwarded port opened successfully"); + } + } + + } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) { + unsigned int remoteid = GET_32BIT(pktin.body); + unsigned int localid = GET_32BIT(pktin.body+4); + struct ssh_channel *c; + + c = find234(ssh_channels, &remoteid, ssh_channelfind); + if (c && c->type == CHAN_SOCKDATA_DORMANT) { + c->remoteid = localid; + c->type = CHAN_SOCKDATA; + pfd_confirm(c->u.pfd.s); + } + } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE || pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) { /* Remote side closes a channel. */ @@ -2474,11 +2681,17 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END); if ((c->closes == 0) && (c->type == CHAN_X11)) { - logevent("X11 connection closed"); + logevent("Forwarded X11 connection terminated"); assert(c->u.x11.s != NULL); x11_close(c->u.x11.s); c->u.x11.s = NULL; } + if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) { + logevent("Forwarded port closed"); + assert(c->u.pfd.s != NULL); + pfd_close(c->u.pfd.s); + c->u.pfd.s = NULL; + } c->closes |= closetype; if (c->closes == 3) { del234(ssh_channels, c); @@ -2497,6 +2710,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) case CHAN_X11: x11_send(c->u.x11.s, p, len); break; + case CHAN_SOCKDATA: + pfd_send(c->u.pfd.s, p, len); + break; case CHAN_AGENT: /* Data for an agent message. Buffer it. */ while (len > 0) { @@ -3093,18 +3309,21 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) { static enum { AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE, - AUTH_PASSWORD + AUTH_PASSWORD, + AUTH_KEYBOARD_INTERACTIVE } method; static enum { AUTH_TYPE_NONE, AUTH_TYPE_PUBLICKEY, AUTH_TYPE_PUBLICKEY_OFFER_LOUD, AUTH_TYPE_PUBLICKEY_OFFER_QUIET, - AUTH_TYPE_PASSWORD + AUTH_TYPE_PASSWORD, + AUTH_TYPE_KEYBOARD_INTERACTIVE } type; - static int gotit, need_pw, can_pubkey, can_passwd; - static int tried_pubkey_config, tried_agent; + static int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; + static int tried_pubkey_config, tried_agent, tried_keyb_inter; static int we_are_in; + static int num_prompts, echo; static char username[100]; static char pwprompt[200]; static char password[100]; @@ -3332,10 +3551,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) in_commasep_string("publickey", methods, methlen); can_passwd = in_commasep_string("password", methods, methlen); + can_passwd = + in_commasep_string("password", methods, methlen); + can_keyb_inter = + in_commasep_string("keyboard-interactive", methods, methlen); } method = 0; + if (!method && can_keyb_inter && !tried_keyb_inter) { + method = AUTH_KEYBOARD_INTERACTIVE; + type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + tried_keyb_inter = TRUE; + + ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(username); + ssh2_pkt_addstring("ssh-connection"); /* service requested */ + ssh2_pkt_addstring("keyboard-interactive"); /* method */ + ssh2_pkt_addstring(""); /* lang */ + ssh2_pkt_addstring(""); + ssh2_pkt_send(); + + crWaitUntilV(ispkt); + if (pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + if (pktin.type == SSH2_MSG_USERAUTH_FAILURE) + gotit = TRUE; + logevent("Keyboard-interactive authentication refused"); + type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + continue; + } + + /* We've got packet with that "interactive" info + dump banners, and set its prompt as ours */ + { + char *name, *inst, *lang, *prompt; + int name_len, inst_len, lang_len, prompt_len; + ssh2_pkt_getstring(&name, &name_len); + ssh2_pkt_getstring(&inst, &inst_len); + ssh2_pkt_getstring(&lang, &lang_len); + if (name_len > 0) + c_write_untrusted(name, name_len); + if (inst_len > 0) + c_write_untrusted(inst, inst_len); + num_prompts = ssh2_pkt_getuint32(); + + ssh2_pkt_getstring(&prompt, &prompt_len); + strncpy(pwprompt, prompt, sizeof(pwprompt)); + need_pw = TRUE; + + echo = ssh2_pkt_getbool(); + } + } + if (!method && can_pubkey && agent_exists() && !tried_agent) { /* * Attempt public-key authentication using Pageant. @@ -3560,7 +3827,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) static int pos = 0; static char c; - c_write_str(pwprompt); + c_write_untrusted(pwprompt, strlen(pwprompt)); ssh_send_ok = 1; pos = 0; @@ -3721,6 +3988,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) ssh_pkt_defersend(); logevent("Sent password"); type = AUTH_TYPE_PASSWORD; + } else if (method == AUTH_KEYBOARD_INTERACTIVE) { + ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + ssh2_pkt_adduint32(num_prompts); + ssh2_pkt_addstring(password); + memset(password, 0, sizeof(password)); + ssh2_pkt_send(); + type = AUTH_TYPE_KEYBOARD_INTERACTIVE; } else { c_write_str ("No supported authentication methods left to try!\r\n"); @@ -3818,6 +4092,97 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } /* + * Enable port forwardings. + */ + { + static char *e; /* preserve across crReturn */ + char type; + int n; + int sport,dport; + char sports[256], dports[256], host[256]; + char buf[1024]; + + ssh_rportfwds = newtree234(ssh_rportcmp_ssh2); + /* Add port forwardings. */ + e = cfg.portfwd; + while (*e) { + type = *e++; + n = 0; + while (*e && *e != '\t') + sports[n++] = *e++; + sports[n] = 0; + if (*e == '\t') + e++; + n = 0; + while (*e && *e != ':') + host[n++] = *e++; + host[n] = 0; + if (*e == ':') + e++; + n = 0; + while (*e) + dports[n++] = *e++; + dports[n] = 0; + e++; + dport = atoi(dports); + sport = atoi(sports); + if (sport && dport) { + if (type == 'L') { + pfd_addforward(host, dport, sport); + sprintf(buf, "Local port %d forwarding to %s:%d", + sport, host, dport); + logevent(buf); + } else { + struct ssh_rportfwd *pf; + pf = smalloc(sizeof(*pf)); + strcpy(pf->dhost, host); + pf->dport = dport; + pf->sport = sport; + if (add234(ssh_rportfwds, pf) != pf) { + sprintf(buf, + "Duplicate remote port forwarding to %s:%s", + host, dport); + logevent(buf); + sfree(pf); + } else { + sprintf(buf, "Requesting remote port %d (forwarded to %s:%d)", + sport, host, dport); + logevent(buf); + ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + ssh2_pkt_addstring("tcpip-forward"); + ssh2_pkt_addbool(1);/* want reply */ + ssh2_pkt_addstring("127.0.0.1"); + ssh2_pkt_adduint32(sport); + ssh2_pkt_send(); + + do { + crWaitUntilV(ispkt); + if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { + unsigned i = ssh2_pkt_getuint32(); + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (!c) + continue;/* nonexistent channel */ + c->v2.remwindow += ssh2_pkt_getuint32(); + } + } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + + if (pktin.type != SSH2_MSG_REQUEST_SUCCESS) { + if (pktin.type != SSH2_MSG_REQUEST_FAILURE) { + bombout(("Server got confused by port forwarding request")); + crReturnV; + } + logevent("Server refused this port forwarding"); + } else { + logevent("Remote port forwarding enabled"); + } + } + } + } + } + } + + /* * Potentially enable agent forwarding. */ if (cfg.agentfwd && agent_exists()) { @@ -3976,6 +4341,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) case CHAN_X11: x11_send(c->u.x11.s, data, length); break; + case CHAN_SOCKDATA: + pfd_send(c->u.pfd.s, data, length); + break; case CHAN_AGENT: while (length > 0) { if (c->u.a.lensofar < 4) { @@ -4059,6 +4427,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) sshfwd_close(c); } else if (c->type == CHAN_AGENT) { sshfwd_close(c); + } else if (c->type == CHAN_SOCKDATA) { + pfd_close(c->u.pfd.s); + sshfwd_close(c); } } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) { unsigned i = ssh2_pkt_getuint32(); @@ -4080,6 +4451,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) break; case CHAN_AGENT: break; + case CHAN_SOCKDATA: + break; } del234(ssh_channels, c); sfree(c->v2.outbuffer); @@ -4107,14 +4480,35 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) continue; /* nonexistent channel */ c->v2.remwindow += ssh2_pkt_getuint32(); try_send = TRUE; + } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + unsigned i = ssh2_pkt_getuint32(); + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ + if (c->type != CHAN_SOCKDATA_DORMANT) + continue; /* dunno why they're confirming this */ + c->remoteid = ssh2_pkt_getuint32(); + c->type = CHAN_SOCKDATA; + c->closes = 0; + c->v2.remwindow = ssh2_pkt_getuint32(); + c->v2.remmaxpkt = ssh2_pkt_getuint32(); + c->v2.outbuffer = NULL; + c->v2.outbuflen = c->v2.outbufsize = 0; + pfd_confirm(c->u.pfd.s); } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) { char *type; int typelen; char *error = NULL; struct ssh_channel *c; + unsigned remid, winsize, pktsize; ssh2_pkt_getstring(&type, &typelen); c = smalloc(sizeof(struct ssh_channel)); + remid = ssh2_pkt_getuint32(); + winsize = ssh2_pkt_getuint32(); + pktsize = ssh2_pkt_getuint32(); + if (typelen == 3 && !memcmp(type, "x11", 3)) { if (!ssh_X11_fwd_enabled) error = "X11 forwarding is not enabled"; @@ -4124,6 +4518,32 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } else { c->type = CHAN_X11; } + } else if (typelen == 15 && + !memcmp(type, "forwarded-tcpip", 15)) { + struct ssh_rportfwd pf, *realpf; + char *dummy; + int dummylen; + ssh2_pkt_getstring(&dummy, &dummylen);/* skip address */ + pf.sport = ssh2_pkt_getuint32(); + realpf = find234(ssh_rportfwds, &pf, NULL); + if (realpf == NULL) { + error = "Remote port is not recognised"; + } else { + char *e = pfd_newconnect(&c->u.pfd.s, realpf->dhost, + realpf->dport, c); + char buf[1024]; + sprintf(buf, "Received remote port open request for %s:%d", + realpf->dhost, realpf->dport); + logevent(buf); + if (e != NULL) { + sprintf(buf, "Port open failed: %s", e); + logevent(buf); + error = "Port open failed"; + } else { + logevent("Forwarded port opened successfully"); + c->type = CHAN_SOCKDATA; + } + } } else if (typelen == 22 && !memcmp(type, "auth-agent@openssh.com", 3)) { if (!ssh_agentfwd_enabled) @@ -4136,7 +4556,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) error = "Unsupported channel type requested"; } - c->remoteid = ssh2_pkt_getuint32(); + c->remoteid = remid; if (error) { ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); ssh2_pkt_adduint32(c->remoteid); @@ -4148,8 +4568,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } else { c->localid = alloc_channel_id(); c->closes = 0; - c->v2.remwindow = ssh2_pkt_getuint32(); - c->v2.remmaxpkt = ssh2_pkt_getuint32(); + c->v2.remwindow = winsize; + c->v2.remmaxpkt = pktsize; c->v2.outbuffer = NULL; c->v2.outbuflen = c->v2.outbufsize = 0; add234(ssh_channels, c); @@ -4306,6 +4726,58 @@ static void ssh_special(Telnet_Special code) } } +void *new_sock_channel(Socket s) +{ + struct ssh_channel *c; + c = smalloc(sizeof(struct ssh_channel)); + + if (c) { + c->remoteid = -1; /* to be set when open confirmed */ + c->localid = alloc_channel_id(); + c->closes = 0; + c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */ + c->u.pfd.s = s; + add234(ssh_channels, c); + } + return c; +} + +void ssh_send_port_open(void *channel, char *hostname, int port, char *org) +{ + struct ssh_channel *c = (struct ssh_channel *)channel; + char buf[1024]; + + sprintf(buf, "Opening forwarded connection to %.512s:%d", hostname, port); + logevent(buf); + + if (ssh_version == 1) { + send_packet(SSH1_MSG_PORT_OPEN, + PKT_INT, c->localid, + PKT_STR, hostname, + PKT_INT, port, + //PKT_STR, , + PKT_END); + } else { + ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + ssh2_pkt_addstring("direct-tcpip"); + ssh2_pkt_adduint32(c->localid); + ssh2_pkt_adduint32(0x8000UL); /* our window size */ + ssh2_pkt_adduint32(0x4000UL); /* our max pkt size */ + ssh2_pkt_addstring(hostname); + ssh2_pkt_adduint32(port); + /* + * We make up values for the originator data; partly it's + * 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. + */ + ssh2_pkt_addstring("client-side-connection"); + ssh2_pkt_adduint32(0); + ssh2_pkt_send(); + } +} + + static Socket ssh_socket(void) { return s;