X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/43cab3f4d345611264d8dc14d1910bd202d605dd..bc06669bcf2c0d331e231d18c0e14023433f9e79:/ssh.c diff --git a/ssh.c b/ssh.c index dafa1489..291d58c4 100644 --- a/ssh.c +++ b/ssh.c @@ -473,6 +473,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, struct Packet *pktin); +static void ssh_channel_destroy(struct ssh_channel *c); /* * Buffer management constants. There are several of these for @@ -588,18 +589,35 @@ struct ssh_channel { * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. * * A channel is completely finished with when all four bits are set. + * + * In SSH-2, the four bits mean: + * + * 1 We have sent SSH2_MSG_CHANNEL_EOF. + * 2 We have sent SSH2_MSG_CHANNEL_CLOSE. + * 4 We have received SSH2_MSG_CHANNEL_EOF. + * 8 We have received SSH2_MSG_CHANNEL_CLOSE. + * + * A channel is completely finished with when we have both sent + * and received CLOSE. + * + * The symbolic constants below use the SSH-2 terminology, which + * is a bit confusing in SSH-1, but we have to use _something_. */ +#define CLOSES_SENT_EOF 1 +#define CLOSES_SENT_CLOSE 2 +#define CLOSES_RCVD_EOF 4 +#define CLOSES_RCVD_CLOSE 8 int closes; /* - * This flag indicates that a close is pending on the outgoing - * side of the channel: that is, wherever we're getting the data - * for this channel has sent us some data followed by EOF. We - * can't actually close the channel until we've finished sending - * the data, so we set this flag instead to remind us to - * initiate the closing process once our buffer is clear. + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. */ - int pending_close; + int pending_eof; /* * True if this channel is causing the underlying connection to be @@ -830,6 +848,7 @@ struct ssh_tag { } state; int size_needed, eof_needed; + int sent_console_eof; struct Packet **queue; int queuelen, queuesize; @@ -4188,70 +4207,50 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, crFinish(1); } -void sshfwd_close(struct ssh_channel *c) +static void ssh_channel_try_eof(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0) + return; /* can't send EOF: pending outgoing data */ + + if (ssh->version == 1) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } else { + struct Packet *pktout; + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_EOF; + if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes)) { + /* + * Also send MSG_CLOSE. + */ + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_CLOSE; + } + } + c->pending_eof = FALSE; /* we've sent it now */ +} + +void sshfwd_write_eof(struct ssh_channel *c) { Ssh ssh = c->ssh; if (ssh->state == SSH_STATE_CLOSED) return; - if (!c->closes) { - /* - * If halfopen is true, we have sent - * CHANNEL_OPEN for this channel, but it hasn't even been - * acknowledged by the server. So we must set a close flag - * on it now, and then when the server acks the channel - * open, we can close it then. - */ - if (!c->halfopen) { - if (ssh->version == 1) { - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, - PKT_END); - c->closes = 1; /* sent MSG_CLOSE */ - } else { - int bytes_to_send = bufchain_size(&c->v.v2.outbuffer); - if (bytes_to_send > 0) { - /* - * If we still have unsent data in our outgoing - * buffer for this channel, we can't actually - * initiate a close operation yet or that data - * will be lost. Instead, set the pending_close - * flag so that when we do clear the buffer - * we'll start closing the channel. - */ - char logmsg[160] = {'\0'}; - sprintf( - logmsg, - "Forwarded port pending to be closed : " - "%d bytes remaining", - bytes_to_send); - logevent(logmsg); - - c->pending_close = TRUE; - } else { - /* - * No locally buffered data, so we can send the - * close message immediately. - */ - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes = 1; /* sent MSG_CLOSE */ - logevent("Nothing left to send, closing channel"); - } - } - } + if (c->closes & CLOSES_SENT_EOF) + return; - if (c->type == CHAN_X11) { - c->u.x11.s = NULL; - logevent("Forwarded X11 connection terminated"); - } else if (c->type == CHAN_SOCKDATA || - c->type == CHAN_SOCKDATA_DORMANT) { - c->u.pfd.s = NULL; - logevent("Forwarded port closed"); - } - } + c->pending_eof = TRUE; + ssh_channel_try_eof(c); } int sshfwd_write(struct ssh_channel *c, char *buf, int len) @@ -4754,7 +4753,7 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = 0; c->type = CHAN_X11; /* identify channel type */ add234(ssh->channels, c); @@ -4784,10 +4783,11 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = 0; c->type = CHAN_AGENT; /* identify channel type */ c->u.a.lensofar = 0; + c->u.a.message = NULL; add234(ssh->channels, c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, PKT_INT, c->remoteid, PKT_INT, c->localid, @@ -4839,7 +4839,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) c->halfopen = FALSE; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = 0; c->type = CHAN_SOCKDATA; /* identify channel type */ add234(ssh->channels, c); @@ -4866,15 +4866,14 @@ static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) pfd_confirm(c->u.pfd.s); } - if (c && c->closes) { + if (c && c->pending_eof) { /* * We have a pending close on this channel, * which we decided on before the server acked * the channel open. So now we know the * remoteid, we can close it again. */ - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, - PKT_INT, c->remoteid, PKT_END); + ssh_channel_try_eof(c); } } @@ -4899,34 +4898,59 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) struct ssh_channel *c; c = find234(ssh->channels, &i, ssh_channelfind); if (c && !c->halfopen) { - int closetype; - closetype = - (pktin->type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2); - - if ((c->closes == 0) && (c->type == CHAN_X11)) { - 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 << 2); /* seen this message */ - if (!(c->closes & closetype)) { - send_packet(ssh, pktin->type, PKT_INT, c->remoteid, - PKT_END); - c->closes |= closetype; /* sent it too */ - } + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE && + !(c->closes & CLOSES_RCVD_EOF)) { + /* + * Received CHANNEL_CLOSE, which we translate into + * outgoing EOF. + */ + int send_close = FALSE; + + c->closes |= CLOSES_RCVD_EOF; + + switch (c->type) { + case CHAN_X11: + if (c->u.x11.s) + x11_send_eof(c->u.x11.s); + else + send_close = TRUE; + case CHAN_SOCKDATA: + if (c->u.pfd.s) + x11_send_eof(c->u.pfd.s); + else + send_close = TRUE; + case CHAN_AGENT: + send_close = TRUE; + } - if (c->closes == 15) { - del234(ssh->channels, c); - sfree(c); - } + if (send_close && !(c->closes & CLOSES_SENT_EOF)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, + PKT_END); + c->closes |= CLOSES_SENT_EOF; + } + } + + if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION && + !(c->closes & CLOSES_RCVD_CLOSE)) { + + if (!(c->closes & CLOSES_SENT_EOF)) { + bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d" + " for which we never sent CHANNEL_CLOSE\n", i)); + } + + c->closes |= CLOSES_RCVD_CLOSE; + } + + if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) && + !(c->closes & CLOSES_SENT_CLOSE)) { + send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, + PKT_INT, c->remoteid, PKT_END); + c->closes |= CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) + ssh_channel_destroy(c); } else { bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n", pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" : @@ -6438,6 +6462,7 @@ static int ssh2_try_send(struct ssh_channel *c) { Ssh ssh = c->ssh; struct Packet *pktout; + int ret; while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { int len; @@ -6462,14 +6487,23 @@ static int ssh2_try_send(struct ssh_channel *c) * After having sent as much data as we can, return the amount * still buffered. */ - return bufchain_size(&c->v.v2.outbuffer); + ret = bufchain_size(&c->v.v2.outbuffer); + + /* + * And if there's no data pending but we need to send an EOF, send + * it. + */ + if (!ret && c->pending_eof) + ssh_channel_try_eof(c); + + return ret; } static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) { int bufsize; - if (c->closes) - return; /* don't send on closing channels */ + if (c->closes & CLOSES_SENT_EOF) + return; /* don't send on channels we've EOFed */ bufsize = ssh2_try_send(c); if (bufsize == 0) { switch (c->type) { @@ -6489,19 +6523,6 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) break; } } - - /* - * If we've emptied the channel's output buffer and there's a - * pending close event, start the channel-closing procedure. - */ - if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) { - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes = 1; - c->pending_close = FALSE; - } } /* @@ -6512,7 +6533,7 @@ static void ssh2_channel_init(struct ssh_channel *c) Ssh ssh = c->ssh; c->localid = alloc_channel_id(ssh); c->closes = 0; - c->pending_close = FALSE; + c->pending_eof = FALSE; c->throttling_conn = FALSE; c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; @@ -6529,11 +6550,11 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin) Ssh ssh = c->ssh; /* - * Never send WINDOW_ADJUST for a channel that the remote side - * already thinks it's closed; there's no point, since it won't - * be sending any more data anyway. + * Never send WINDOW_ADJUST for a channel that the remote side has + * already sent EOF on; there's no point, since it won't be + * sending any more data anyway. */ - if (c->closes != 0) + if (c->closes & CLOSES_RCVD_EOF) return; /* @@ -6699,7 +6720,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) c = ssh2_channel_msg(ssh, pktin); if (!c) return; - if (!c->closes) { + if (!(c->closes & CLOSES_SENT_EOF)) { c->v.v2.remwindow += ssh_pkt_getuint32(pktin); ssh2_try_send_and_unthrottle(ssh, c); } @@ -6771,6 +6792,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) ssh_agentf_callback, c)) ssh_agentf_callback(c, reply, replylen); sfree(c->u.a.message); + c->u.a.message = NULL; c->u.a.lensofar = 0; } } @@ -6808,93 +6830,149 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) } } -static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) +static void ssh_channel_destroy(struct ssh_channel *c) { - struct ssh_channel *c; + Ssh ssh = c->ssh; - c = ssh2_channel_msg(ssh, pktin); - if (!c) - return; + switch (c->type) { + case CHAN_MAINSESSION: + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; + case CHAN_X11: + if (c->u.x11.s != NULL) + x11_close(c->u.x11.s); + logevent("Forwarded X11 connection terminated"); + break; + case CHAN_AGENT: + sfree(c->u.a.message); + break; + case CHAN_SOCKDATA: + if (c->u.pfd.s != NULL) + pfd_close(c->u.pfd.s); + logevent("Forwarded port closed"); + break; + } + + del234(ssh->channels, c); + if (ssh->version == 2) + bufchain_clear(&c->v.v2.outbuffer); + sfree(c); + + /* + * See if that was the last channel left open. + * (This is only our termination condition if we're + * not running in -N mode.) + */ + if (ssh->version == 2 && + !conf_get_int(ssh->conf, CONF_ssh_no_shell) && + count234(ssh->channels) == 0) { + /* + * We used to send SSH_MSG_DISCONNECT here, + * because I'd believed that _every_ conforming + * SSH-2 connection had to end with a disconnect + * being sent by at least one side; apparently + * I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut + * when you're done, and indeed OpenSSH feels + * this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + } +} + +static void ssh2_channel_check_close(struct ssh_channel *c) +{ + Ssh ssh = c->ssh; + struct Packet *pktout; + + if ((c->closes & (CLOSES_SENT_EOF | CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) + == (CLOSES_SENT_EOF | CLOSES_RCVD_EOF)) { + /* + * We have both sent and received EOF, which means the channel + * is in final wind-up. But we haven't sent CLOSE, so let's. + */ + pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + ssh2_pkt_adduint32(pktout, c->remoteid); + ssh2_pkt_send(ssh, pktout); + c->closes |= CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { + /* + * We have both sent and received CLOSE, which means we're + * completely done with the channel. + */ + ssh_channel_destroy(c); + } +} + +static void ssh2_channel_got_eof(struct ssh_channel *c) +{ + if (c->closes & CLOSES_RCVD_EOF) + return; /* already seen EOF */ + c->closes |= CLOSES_RCVD_EOF; 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); - c->u.x11.s = NULL; - sshfwd_close(c); + x11_send_eof(c->u.x11.s); } else if (c->type == CHAN_AGENT) { - sshfwd_close(c); + /* Manufacture an outgoing EOF in response to the incoming one. */ + sshfwd_write_eof(c); } else if (c->type == CHAN_SOCKDATA) { - pfd_close(c->u.pfd.s); - c->u.pfd.s = NULL; - sshfwd_close(c); + pfd_send_eof(c->u.pfd.s); + } else if (c->type == CHAN_MAINSESSION) { + Ssh ssh = c->ssh; + + if (!ssh->sent_console_eof && from_backend_eof(ssh->frontend)) { + /* + * The front end wants us to close the outgoing side of the + * connection as soon as we see EOF from the far end. + */ + sshfwd_write_eof(c); + } + ssh->sent_console_eof = TRUE; } + + ssh2_channel_check_close(c); +} + +static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) +{ + struct ssh_channel *c; + + c = ssh2_channel_msg(ssh, pktin); + if (!c) + return; + ssh2_channel_got_eof(c); } static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; - struct Packet *pktout; c = ssh2_channel_msg(ssh, pktin); if (!c) return; - /* Do pre-close processing on the channel. */ - switch (c->type) { - case CHAN_MAINSESSION: - ssh->mainchan = NULL; - update_specials_menu(ssh->frontend); - break; - case CHAN_X11: - if (c->u.x11.s != NULL) - x11_close(c->u.x11.s); - sshfwd_close(c); - break; - case CHAN_AGENT: - sshfwd_close(c); - break; - case CHAN_SOCKDATA: - if (c->u.pfd.s != NULL) - pfd_close(c->u.pfd.s); - sshfwd_close(c); - break; - } - if (c->closes == 0) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } - del234(ssh->channels, c); - bufchain_clear(&c->v.v2.outbuffer); - sfree(c); /* - * See if that was the last channel left open. - * (This is only our termination condition if we're - * not running in -N mode.) + * When we receive CLOSE on a channel, we assume it comes with an + * implied EOF if we haven't seen EOF yet. */ - if (!conf_get_int(ssh->conf, CONF_ssh_no_shell) && count234(ssh->channels) == 0) { - /* - * We used to send SSH_MSG_DISCONNECT here, - * because I'd believed that _every_ conforming - * SSH-2 connection had to end with a disconnect - * being sent by at least one side; apparently - * I was wrong and it's perfectly OK to - * unceremoniously slam the connection shut - * when you're done, and indeed OpenSSH feels - * this is more polite than sending a - * DISCONNECT. So now we don't. - */ - ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); + ssh2_channel_got_eof(c); + + /* + * Now process the actual close. + */ + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + ssh2_channel_check_close(c); } } static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) { struct ssh_channel *c; - struct Packet *pktout; c = ssh2_channel_msg(ssh, pktin); if (!c) @@ -6908,17 +6986,8 @@ static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); if (c->u.pfd.s) pfd_confirm(c->u.pfd.s); - if (c->closes) { - /* - * We have a pending close on this channel, - * which we decided on before the server acked - * the channel open. So now we know the - * remoteid, we can close it again. - */ - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } + if (c->pending_eof) + ssh_channel_try_eof(c); } static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) @@ -9367,6 +9436,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, bufchain_init(&ssh->queued_incoming_data); ssh->frozen = FALSE; ssh->username = NULL; + ssh->sent_console_eof = FALSE; *backend_handle = ssh; @@ -9619,7 +9689,7 @@ static int ssh_sendbuffer(void *handle) if (ssh->version == 1) { return override_value; } else if (ssh->version == 2) { - if (!ssh->mainchan || ssh->mainchan->closes > 0) + if (!ssh->mainchan) return override_value; else return (override_value + @@ -9768,9 +9838,7 @@ static void ssh_special(void *handle, Telnet_Special code) if (ssh->version == 1) { send_packet(ssh, SSH1_CMSG_EOF, PKT_END); } else if (ssh->mainchan) { - struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); - ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid); - ssh2_pkt_send(ssh, pktout); + sshfwd_write_eof(ssh->mainchan); ssh->send_ok = 0; /* now stop trying to read from stdin */ } logevent("Sent EOF message");