X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/6abb6cd7dbb5f5846931d11653de968dd8c4f9c7..4b1e8accb59e8f3b35d24f7ae207325a49cb3cc6:/ssh.c diff --git a/ssh.c b/ssh.c index 26b26c32..5a7dcf24 100644 --- a/ssh.c +++ b/ssh.c @@ -19,8 +19,9 @@ if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \ fprintf(stderr, "%s\n", s); } -#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, sk_close(s), \ - s = NULL, connection_fatal msg ) +#define bombout(msg) ( ssh_state = SSH_STATE_CLOSED, \ + (s ? sk_close(s), s = NULL : (void)0), \ + connection_fatal msg ) #define SSH1_MSG_DISCONNECT 1 /* 0x1 */ #define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ @@ -46,10 +47,15 @@ #define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ #define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ #define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ +#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ +#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ +#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ #define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ #define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ -#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ #define SSH1_MSG_IGNORE 32 /* 0x20 */ +#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ +#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ +#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ #define SSH1_MSG_DEBUG 36 /* 0x24 */ #define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ #define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ @@ -160,6 +166,11 @@ extern const struct ssh_cipher ssh_des; extern const struct ssh_cipher ssh_blowfish_ssh1; extern const struct ssh_cipher ssh_blowfish_ssh2; +extern char *x11_init (Socket *, char *, void *); +extern void x11_close (Socket); +extern void x11_send (Socket , char *, int); +extern void x11_invent_auth(char *, int, char *, int); + /* * Ciphers for SSH2. We miss out single-DES because it isn't * supported; also 3DES and Blowfish are both done differently from @@ -201,6 +212,12 @@ extern const struct ssh_compress ssh_zlib; const static struct ssh_compress *compressions[] = { &ssh_zlib, &ssh_comp_none }; +enum { /* channel types */ + CHAN_MAINSESSION, + CHAN_X11, + CHAN_AGENT, +}; + /* * 2-3-4 tree storing channels. */ @@ -208,17 +225,20 @@ struct ssh_channel { unsigned remoteid, localid; int type; int closes; + struct ssh2_data_channel { + unsigned char *outbuffer; + unsigned outbuflen, outbufsize; + unsigned remwindow, remmaxpkt; + } v2; union { struct ssh_agent_channel { unsigned char *message; unsigned char msglen[4]; int lensofar, totallen; } a; - struct ssh2_data_channel { - unsigned char *outbuffer; - unsigned outbuflen, outbufsize; - unsigned remwindow, remmaxpkt; - } v2; + struct ssh_x11_channel { + Socket s; + } x11; } u; }; @@ -238,6 +258,7 @@ static Socket s = NULL; static unsigned char session_key[32]; static int ssh1_compressing; static int ssh_agentfwd_enabled; +static int ssh_X11_fwd_enabled; static const struct ssh_cipher *cipher = NULL; static const struct ssh_cipher *cscipher = NULL; static const struct ssh_cipher *sccipher = NULL; @@ -252,6 +273,7 @@ int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL; static char *savedhost; static int savedport; static int ssh_send_ok; +static int ssh_echoing, ssh_editing; static tree234 *ssh_channels; /* indexed by local id */ static struct ssh_channel *mainchan; /* primary session channel */ @@ -275,6 +297,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt); static void ssh2_protocol(unsigned char *in, int inlen, int ispkt); static void ssh_size(void); static void ssh_special (Telnet_Special); +static void ssh2_try_send(struct ssh_channel *c); +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len); static int (*s_rdpkt)(unsigned char **data, int *datalen); @@ -794,15 +818,18 @@ static void sha_string(SHA_State *s, void *str, int len) { /* * SSH2 packet construction functions. */ -static void ssh2_pkt_adddata(void *data, int len) { - pktout.length += len; - if (pktout.maxlen < pktout.length) { - pktout.maxlen = pktout.length + 256; +static void ssh2_pkt_ensure(int length) { + if (pktout.maxlen < length) { + pktout.maxlen = length + 256; pktout.data = (pktout.data == NULL ? smalloc(pktout.maxlen+APIEXTRA) : srealloc(pktout.data, pktout.maxlen+APIEXTRA)); if (!pktout.data) fatalbox("Out of memory"); } +} +static void ssh2_pkt_adddata(void *data, int len) { + pktout.length += len; + ssh2_pkt_ensure(pktout.length); memcpy(pktout.data+pktout.length-len, data, len); } static void ssh2_pkt_addbyte(unsigned char byte) { @@ -896,6 +923,8 @@ static void ssh2_pkt_send(void) { cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ padding = 4; padding += (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk; + maclen = csmac ? csmac->len : 0; + ssh2_pkt_ensure(pktout.length + padding + maclen); pktout.data[4] = padding; for (i = 0; i < padding; i++) pktout.data[pktout.length + i] = random_byte(); @@ -914,7 +943,6 @@ static void ssh2_pkt_send(void) { if (cscipher) cscipher->encrypt(pktout.data, pktout.length + padding); - maclen = csmac ? csmac->len : 0; sk_write(s, pktout.data, pktout.length + padding + maclen); } @@ -1122,7 +1150,14 @@ static void ssh_gotdata(unsigned char *data, int datalen) } static int ssh_receive(Socket skt, int urgent, char *data, int len) { - if (!len) { + if (urgent==3) { + /* A socket error has occurred. */ + ssh_state = SSH_STATE_CLOSED; + sk_close(s); + s = NULL; + connection_fatal(data); + return 0; + } else if (!len) { /* Connection has closed. */ ssh_state = SSH_STATE_CLOSED; sk_close(s); @@ -1184,7 +1219,7 @@ static char *connect_to_host(char *host, int port, char **realhost) /* * Open socket. */ - s = sk_new(addr, port, ssh_receive); + s = sk_new(addr, port, 0, 1, ssh_receive); if ( (err = sk_socket_error(s)) ) return err; @@ -1719,6 +1754,36 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt) crFinish(1); } +void sshfwd_close(struct ssh_channel *c) { + if (c) { + if (ssh_version == 1) { + send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, PKT_END); + } else { + ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } + c->closes = 1; + if (c->type == CHAN_X11) { + c->u.x11.s = NULL; + logevent("X11 connection terminated"); + } + } +} + +void sshfwd_write(struct ssh_channel *c, char *buf, int len) { + if (ssh_version == 1) { + send_packet(SSH1_MSG_CHANNEL_DATA, + PKT_INT, c->remoteid, + PKT_INT, len, + PKT_DATA, buf, len, + PKT_END); + } else { + ssh2_add_channel_data(c, buf, len); + ssh2_try_send(c); + } +} + static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { crBegin; @@ -1745,6 +1810,26 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { } } + if (cfg.x11_forward) { + char proto[20], data[64]; + logevent("Requesting X11 forwarding"); + x11_invent_auth(proto, sizeof(proto), data, sizeof(data)); + send_packet(SSH1_CMSG_X11_REQUEST_FORWARDING, + PKT_STR, proto, PKT_STR, data, + PKT_INT, 0, + PKT_END); + do { crReturnV; } while (!ispkt); + if (pktin.type != SSH1_SMSG_SUCCESS && pktin.type != SSH1_SMSG_FAILURE) { + bombout(("Protocol confusion")); + crReturnV; + } else if (pktin.type == SSH1_SMSG_FAILURE) { + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh_X11_fwd_enabled = TRUE; + } + } + if (!cfg.nopty) { send_packet(SSH1_CMSG_REQUEST_PTY, PKT_STR, cfg.termtype, @@ -1759,8 +1844,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { crReturnV; } else if (pktin.type == SSH1_SMSG_FAILURE) { c_write("Server refused to allocate pty\r\n", 32); + ssh_editing = ssh_echoing = 1; } logevent("Allocated pty"); + } else { + ssh_editing = ssh_echoing = 1; } if (cfg.compression) { @@ -1790,9 +1878,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { if (eof_needed) ssh_special(TS_EOF); + ldisc_send(NULL, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; ssh_channels = newtree234(ssh_channelcmp); - begin_session(); while (1) { crReturnV; if (ispkt) { @@ -1805,6 +1893,47 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { ssh_state = SSH_STATE_CLOSED; logevent("Received disconnect request"); crReturnV; + } else if (pktin.type == SSH1_SMSG_X11_OPEN) { + /* Remote side is trying to open a channel to talk to our + * X-Server. Give them back a local channel number. */ + unsigned i; + struct ssh_channel *c, *d; + enum234 e; + + logevent("Received X11 connect request"); + /* Refuse if X11 forwarding is disabled. */ + if (!ssh_X11_fwd_enabled) { + send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, GET_32BIT(pktin.body), + PKT_END); + logevent("Rejected X11 connect request"); + } else { + c = smalloc(sizeof(struct ssh_channel)); + + if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) { + logevent("opening X11 forward connection failed"); + sfree(c); + send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE, + PKT_INT, GET_32BIT(pktin.body), + PKT_END); + } else { + logevent("opening X11 forward connection succeeded"); + for (i=1, d = first234(ssh_channels, &e); d; d = next234(&e)) { + if (d->localid > i) + break; /* found a free number */ + i = d->localid + 1; + } + c->remoteid = GET_32BIT(pktin.body); + c->localid = i; + c->closes = 0; + c->type = CHAN_X11; /* 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("Opened X11 forward channel"); + } + } } else if (pktin.type == SSH1_SMSG_AGENT_OPEN) { /* Remote side is trying to open a channel to talk to our * agent. Give them back a local channel number. */ @@ -1828,7 +1957,7 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { c->remoteid = GET_32BIT(pktin.body); c->localid = i; c->closes = 0; - c->type = SSH1_SMSG_AGENT_OPEN;/* identify channel type */ + c->type = CHAN_AGENT; /* identify channel type */ c->u.a.lensofar = 0; add234(ssh_channels, c); send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, @@ -1845,6 +1974,12 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { int closetype; closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END); + if ((c->closes == 0) && (c->type == CHAN_X11)) { + logevent("X11 connection closed"); + assert(c->u.x11.s != NULL); + x11_close(c->u.x11.s); + c->u.x11.s = NULL; + } c->closes |= closetype; if (c->closes == 3) { del234(ssh_channels, c); @@ -1860,7 +1995,10 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) { c = find234(ssh_channels, &i, ssh_channelfind); if (c) { switch(c->type) { - case SSH1_SMSG_AGENT_OPEN: + case CHAN_X11: + x11_send(c->u.x11.s, p, len); + break; + case CHAN_AGENT: /* Data for an agent message. Buffer it. */ while (len > 0) { if (c->u.a.lensofar < 4) { @@ -1959,14 +2097,14 @@ static int in_commasep_string(char *needle, char *haystack, int haylen) { /* * SSH2 key creation method. */ -static void ssh2_mkkey(Bignum K, char *H, char chr, char *keyspace) { +static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, char *keyspace) { SHA_State s; /* First 20 bytes. */ SHA_Init(&s); sha_mpint(&s, K); SHA_Bytes(&s, H, 20); SHA_Bytes(&s, &chr, 1); - SHA_Bytes(&s, H, 20); + SHA_Bytes(&s, sessid, 20); SHA_Final(&s, keyspace); /* Next 20 bytes. */ SHA_Init(&s); @@ -1996,6 +2134,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) static int hostkeylen, siglen; static void *hkey; /* actual host key */ static unsigned char exchange_hash[20]; + static unsigned char first_exchange_hash[20]; static unsigned char keyspace[40]; static const struct ssh_cipher *preferred_cipher; static const struct ssh_compress *preferred_comp; @@ -2252,8 +2391,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) fingerprint = hostkey->fingerprint(hkey); verify_ssh_host_key(savedhost, savedport, hostkey->keytype, keystr, fingerprint); - logevent("Host key fingerprint is:"); - logevent(fingerprint); + if (first_kex) { /* don't bother logging this in rekeys */ + logevent("Host key fingerprint is:"); + logevent(fingerprint); + } sfree(fingerprint); sfree(keystr); hostkey->freekey(hkey); @@ -2276,14 +2417,23 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) cscomp->compress_init(); sccomp->decompress_init(); /* - * Set IVs after keys. + * Set IVs after keys. Here we use the exchange hash from the + * _first_ key exchange. */ - ssh2_mkkey(K, exchange_hash, 'C', keyspace); cscipher->setcskey(keyspace); - ssh2_mkkey(K, exchange_hash, 'D', keyspace); cscipher->setsckey(keyspace); - ssh2_mkkey(K, exchange_hash, 'A', keyspace); cscipher->setcsiv(keyspace); - ssh2_mkkey(K, exchange_hash, 'B', keyspace); sccipher->setsciv(keyspace); - ssh2_mkkey(K, exchange_hash, 'E', keyspace); csmac->setcskey(keyspace); - ssh2_mkkey(K, exchange_hash, 'F', keyspace); scmac->setsckey(keyspace); + if (first_kex) + memcpy(first_exchange_hash, exchange_hash, sizeof(exchange_hash)); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'C', keyspace); + cscipher->setcskey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'D', keyspace); + sccipher->setsckey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'A', keyspace); + cscipher->setcsiv(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'B', keyspace); + sccipher->setsciv(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'E', keyspace); + csmac->setcskey(keyspace); + ssh2_mkkey(K, exchange_hash, first_exchange_hash, 'F', keyspace); + scmac->setsckey(keyspace); /* * If this is the first key exchange phase, we must pass the @@ -2307,12 +2457,52 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt) do { crReturn(1); } while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT)); + logevent("Server initiated key re-exchange"); goto begin_key_exchange; crFinish(1); } /* + * Add data to an SSH2 channel output buffer. + */ +static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len) { + if (c->v2.outbufsize < + c->v2.outbuflen + len) { + c->v2.outbufsize = + c->v2.outbuflen + len + 1024; + c->v2.outbuffer = srealloc(c->v2.outbuffer, + c->v2.outbufsize); + } + memcpy(c->v2.outbuffer + c->v2.outbuflen, + buf, len); + c->v2.outbuflen += len; +} + +/* + * Attempt to send data on an SSH2 channel. + */ +static void ssh2_try_send(struct ssh_channel *c) { + while (c->v2.remwindow > 0 && + c->v2.outbuflen > 0) { + unsigned len = c->v2.remwindow; + if (len > c->v2.outbuflen) + len = c->v2.outbuflen; + if (len > c->v2.remmaxpkt) + len = c->v2.remmaxpkt; + ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_addstring_start(); + ssh2_pkt_addstring_data(c->v2.outbuffer, len); + ssh2_pkt_send(); + c->v2.outbuflen -= len; + memmove(c->v2.outbuffer, c->v2.outbuffer+len, + c->v2.outbuflen); + c->v2.remwindow -= len; + } +} + +/* * Handle the SSH2 userauth and connection layers. */ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) @@ -2486,13 +2676,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) crReturnV; } mainchan->remoteid = ssh2_pkt_getuint32(); - mainchan->u.v2.remwindow = ssh2_pkt_getuint32(); - mainchan->u.v2.remmaxpkt = ssh2_pkt_getuint32(); - mainchan->u.v2.outbuffer = NULL; - mainchan->u.v2.outbuflen = mainchan->u.v2.outbufsize = 0; + mainchan->type = CHAN_MAINSESSION; + mainchan->closes = 0; + mainchan->v2.remwindow = ssh2_pkt_getuint32(); + mainchan->v2.remmaxpkt = ssh2_pkt_getuint32(); + mainchan->v2.outbuffer = NULL; + mainchan->v2.outbuflen = mainchan->v2.outbufsize = 0; + ssh_channels = newtree234(ssh_channelcmp); + add234(ssh_channels, mainchan); logevent("Opened channel for session"); /* + * Potentially enable X11 forwarding. + */ + if (cfg.x11_forward) { + char proto[20], data[64]; + logevent("Requesting X11 forwarding"); + x11_invent_auth(proto, sizeof(proto), data, sizeof(data)); + ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(mainchan->remoteid); + ssh2_pkt_addstring("x11-req"); + ssh2_pkt_addbool(1); /* want reply */ + ssh2_pkt_addbool(0); /* many connections */ + ssh2_pkt_addstring(proto); + ssh2_pkt_addstring(data); + ssh2_pkt_adduint32(0); /* screen number */ + 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_CHANNEL_SUCCESS) { + if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Server got confused by X11 forwarding request")); + crReturnV; + } + logevent("X11 forwarding refused"); + } else { + logevent("X11 forwarding enabled"); + ssh_X11_fwd_enabled = TRUE; + } + } + + /* * Now allocate a pty for the session. */ if (!cfg.nopty) { @@ -2513,10 +2748,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) do { crWaitUntilV(ispkt); if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - /* FIXME: be able to handle other channels here */ - if (ssh2_pkt_getuint32() != mainchan->localid) - continue; /* wrong channel */ - mainchan->u.v2.remwindow += ssh2_pkt_getuint32(); + 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); @@ -2526,9 +2763,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) crReturnV; } c_write("Server refused to allocate pty\r\n", 32); + ssh_editing = ssh_echoing = 1; } else { logevent("Allocated pty"); } + } else { + ssh_editing = ssh_echoing = 1; } /* @@ -2548,10 +2788,12 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) do { crWaitUntilV(ispkt); if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - /* FIXME: be able to handle other channels here */ - if (ssh2_pkt_getuint32() != mainchan->localid) - continue; /* wrong channel */ - mainchan->u.v2.remwindow += ssh2_pkt_getuint32(); + 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_CHANNEL_SUCCESS) { @@ -2574,8 +2816,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) /* * Transfer data! */ + ldisc_send(NULL, 0); /* cause ldisc to notice changes */ ssh_send_ok = 1; - begin_session(); while (1) { static int try_send; crReturnV; @@ -2585,23 +2827,32 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) { char *data; int length; - /* FIXME: be able to handle other channels here */ - if (ssh2_pkt_getuint32() != mainchan->localid) - continue; /* wrong channel */ + unsigned i = ssh2_pkt_getuint32(); + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ if (pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA && ssh2_pkt_getuint32() != SSH2_EXTENDED_DATA_STDERR) continue; /* extended but not stderr */ ssh2_pkt_getstring(&data, &length); if (data) { - from_backend(pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA, - data, length); + switch (c->type) { + case CHAN_MAINSESSION: + from_backend(pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA, + data, length); + break; + case CHAN_X11: + x11_send(c->u.x11.s, data, length); + break; + } /* - * Enlarge the window again at the remote side, - * just in case it ever runs down and they fail - * to send us any more data. + * Enlarge the window again at the remote + * side, just in case it ever runs down and + * they fail to send us any more data. */ ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); - ssh2_pkt_adduint32(mainchan->remoteid); + ssh2_pkt_adduint32(c->remoteid); ssh2_pkt_adduint32(length); ssh2_pkt_send(); } @@ -2612,16 +2863,50 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) { continue; /* exit status et al; ignore (FIXME?) */ } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) { - continue; /* remote sends EOF; ignore */ + 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_X11) { + /* + * Remote EOF on an X11 channel means we should + * wrap up and close the channel ourselves. + */ + x11_close(c->u.x11.s); + sshfwd_close(c); + } } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) { - /* FIXME: be able to handle other channels here */ - if (ssh2_pkt_getuint32() != mainchan->localid) - continue; /* wrong channel */ - ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(mainchan->remoteid); - ssh2_pkt_send(); - /* FIXME: mark the channel as closed */ - if (1 /* FIXME: "all channels are closed" */) { + unsigned i = ssh2_pkt_getuint32(); + struct ssh_channel *c; + enum234 e; + + c = find234(ssh_channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ + if (c->closes == 0) { + ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_send(); + } + /* Do pre-close processing on the channel. */ + switch (c->type) { + case CHAN_MAINSESSION: + break; /* nothing to see here, move along */ + case CHAN_X11: + break; + } + del234(ssh_channels, c); + sfree(c->v2.outbuffer); + sfree(c); + + /* + * See if that was the last channel left open. + */ + c = first234(ssh_channels, &e); + if (!c) { logevent("All channels closed. Disconnecting"); ssh2_pkt_init(SSH2_MSG_DISCONNECT); ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION); @@ -2633,11 +2918,67 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) } continue; /* remote sends close; ignore (FIXME) */ } else if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { - /* FIXME: be able to handle other channels here */ - if (ssh2_pkt_getuint32() != mainchan->localid) - continue; /* wrong channel */ - mainchan->u.v2.remwindow += ssh2_pkt_getuint32(); + unsigned i = ssh2_pkt_getuint32(); + struct ssh_channel *c; + c = find234(ssh_channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ + mainchan->v2.remwindow += ssh2_pkt_getuint32(); try_send = TRUE; + } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) { + char *type; + int typelen; + char *error = NULL; + struct ssh_channel *c; + ssh2_pkt_getstring(&type, &typelen); + c = smalloc(sizeof(struct ssh_channel)); + + if (typelen == 3 && !memcmp(type, "x11", 3)) { + if (!ssh_X11_fwd_enabled) + error = "X11 forwarding is not enabled"; + else if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) { + error = "Unable to open an X11 connection"; + } else { + c->type = CHAN_X11; + } + } else { + error = "Unsupported channel type requested"; + } + + c->remoteid = ssh2_pkt_getuint32(); + if (error) { + ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_adduint32(SSH2_OPEN_CONNECT_FAILED); + ssh2_pkt_addstring(error); + ssh2_pkt_addstring("en"); /* language tag */ + ssh2_pkt_send(); + sfree(c); + } else { + struct ssh_channel *d; + unsigned i; + enum234 e; + + for (i=1, d = first234(ssh_channels, &e); d; + d = next234(&e)) { + if (d->localid > i) + break; /* found a free number */ + i = d->localid + 1; + } + c->localid = i; + 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; + add234(ssh_channels, c); + ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + ssh2_pkt_adduint32(c->remoteid); + ssh2_pkt_adduint32(c->localid); + ssh2_pkt_adduint32(0x8000UL); /* our window size */ + ssh2_pkt_adduint32(0x4000UL); /* our max pkt size */ + ssh2_pkt_send(); + } } else { bombout(("Strange packet received: type %d", pktin.type)); crReturnV; @@ -2646,40 +2987,17 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt) /* * We have spare data. Add it to the channel buffer. */ - if (mainchan->u.v2.outbufsize < - mainchan->u.v2.outbuflen + inlen) { - mainchan->u.v2.outbufsize = - mainchan->u.v2.outbuflen + inlen + 1024; - mainchan->u.v2.outbuffer = srealloc(mainchan->u.v2.outbuffer, - mainchan->u.v2.outbufsize); - } - memcpy(mainchan->u.v2.outbuffer + mainchan->u.v2.outbuflen, - in, inlen); - mainchan->u.v2.outbuflen += inlen; + ssh2_add_channel_data(mainchan, in, inlen); try_send = TRUE; } if (try_send) { + enum234 e; + struct ssh_channel *c; /* - * Try to send data on the channel if we can. (FIXME: - * on _all_ channels.) + * Try to send data on all channels if we can. */ - while (mainchan->u.v2.remwindow > 0 && - mainchan->u.v2.outbuflen > 0) { - unsigned len = mainchan->u.v2.remwindow; - if (len > mainchan->u.v2.outbuflen) - len = mainchan->u.v2.outbuflen; - if (len > mainchan->u.v2.remmaxpkt) - len = mainchan->u.v2.remmaxpkt; - ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); - ssh2_pkt_adduint32(mainchan->remoteid); - ssh2_pkt_addstring_start(); - ssh2_pkt_addstring_data(mainchan->u.v2.outbuffer, len); - ssh2_pkt_send(); - mainchan->u.v2.outbuflen -= len; - memmove(mainchan->u.v2.outbuffer, mainchan->u.v2.outbuffer+len, - mainchan->u.v2.outbuflen); - mainchan->u.v2.remwindow -= len; - } + for (c = first234(ssh_channels, &e); c; c = next234(&e)) + ssh2_try_send(c); } } @@ -2710,6 +3028,8 @@ static char *ssh_init (char *host, int port, char **realhost) { #endif ssh_send_ok = 0; + ssh_editing = 0; + ssh_echoing = 0; p = connect_to_host(host, port, realhost); if (p != NULL) @@ -2805,6 +3125,12 @@ static Socket ssh_socket(void) { return s; } static int ssh_sendok(void) { return ssh_send_ok; } +static int ssh_ldisc(int option) { + if (option == LD_ECHO) return ssh_echoing; + if (option == LD_EDIT) return ssh_editing; + return FALSE; +} + Backend ssh_backend = { ssh_init, ssh_send, @@ -2812,5 +3138,6 @@ Backend ssh_backend = { ssh_special, ssh_socket, ssh_sendok, + ssh_ldisc, 22 };