Revamp of EOF handling in all network connections, pipes and other
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 13 Sep 2011 11:44:03 +0000 (11:44 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 13 Sep 2011 11:44:03 +0000 (11:44 +0000)
data channels. Should comprehensively fix 'half-closed', in principle,
though it's a big and complicated change and so there's a good chance
I've made at least one mistake somewhere.

All connections should now be rigorous about propagating end-of-file
(or end-of-data-stream, or socket shutdown, or whatever) independently
in both directions, except in frontends with no mechanism for sending
explicit EOF (e.g. interactive terminal windows) or backends which are
basically always used for interactive sessions so it's unlikely that
an application would be depending on independent EOF (telnet, rlogin).

EOF should now never accidentally be sent while there's still buffered
data to go out before it. (May help fix 'portfwd-corrupt', and also I
noticed recently that the ssh main session channel can accidentally
have MSG_EOF sent before the output bufchain is clear, leading to
embarrassment when it subsequently does send the output).

git-svn-id: svn://svn.tartarus.org/sgt/putty@9279 cda61777-01e9-0310-a592-d414129be87e

23 files changed:
network.h
portfwd.c
proxy.c
proxy.h
pscp.c
psftp.c
putty.h
raw.c
rlogin.c
ssh.c
ssh.h
telnet.c
unix/gtkwin.c
unix/uxnet.c
unix/uxplink.c
unix/uxproxy.c
windows/window.c
windows/winhandl.c
windows/winnet.c
windows/winplink.c
windows/winproxy.c
windows/winstuff.h
x11fwd.c

index d9f4886..45f4e2a 100644 (file)
--- a/network.h
+++ b/network.h
@@ -37,6 +37,7 @@ struct socket_function_table {
     void (*close) (Socket s);
     int (*write) (Socket s, const char *data, int len);
     int (*write_oob) (Socket s, const char *data, int len);
+    void (*write_eof) (Socket s);
     void (*flush) (Socket s);
     void (*set_private_ptr) (Socket s, void *ptr);
     void *(*get_private_ptr) (Socket s);
@@ -140,6 +141,7 @@ Socket sk_register(OSSocket sock, Plug plug);
 #define sk_close(s) (((*s)->close) (s))
 #define sk_write(s,buf,len) (((*s)->write) (s, buf, len))
 #define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len))
+#define sk_write_eof(s) (((*s)->write_eof) (s))
 #define sk_flush(s) (((*s)->flush) (s))
 
 #ifdef DEFINE_PLUG_METHOD_MACROS
index e7e9d63..cd66a98 100644 (file)
--- a/portfwd.c
+++ b/portfwd.c
@@ -65,10 +65,14 @@ static int pfd_closing(Plug plug, const char *error_msg, int error_code,
      * We have no way to communicate down the forwarded connection,
      * so if an error occurred on the socket, we just ignore it
      * and treat it like a proper close.
+     *
+     * FIXME: except we could initiate a full close here instead of
+     * just an outgoing EOF? ssh.c currently has no API for that, but
+     * it could.
      */
     if (pr->c)
-       sshfwd_close(pr->c);
-    pfd_close(pr->s);
+       sshfwd_write_eof(pr->c);
+
     return 1;
 }
 
@@ -537,6 +541,10 @@ int pfd_send(Socket s, char *data, int len)
     return sk_write(s, data, len);
 }
 
+void pfd_send_eof(Socket s)
+{
+    sk_write_eof(s);
+}
 
 void pfd_confirm(Socket s)
 {
diff --git a/proxy.c b/proxy.c
index 0b6d9d5..051beb7 100644 (file)
--- a/proxy.c
+++ b/proxy.c
@@ -65,6 +65,9 @@ void proxy_activate (Proxy_Socket p)
      */
     if (p->pending_flush) sk_flush(p->sub_socket);
 
+    /* if we have a pending EOF to send, send it */
+    if (p->pending_eof) sk_write_eof(p->sub_socket);
+
     /* if the backend wanted the socket unfrozen, try to unfreeze.
      * our set_frozen handler will flush buffered receive data before
      * unfreezing the actual underlying socket.
@@ -117,6 +120,17 @@ static int sk_proxy_write_oob (Socket s, const char *data, int len)
     return sk_write_oob(ps->sub_socket, data, len);
 }
 
+static void sk_proxy_write_eof (Socket s)
+{
+    Proxy_Socket ps = (Proxy_Socket) s;
+
+    if (ps->state != PROXY_STATE_ACTIVE) {
+        ps->pending_eof = 1;
+       return;
+    }
+    sk_write_eof(ps->sub_socket);
+}
+
 static void sk_proxy_flush (Socket s)
 {
     Proxy_Socket ps = (Proxy_Socket) s;
@@ -372,6 +386,7 @@ Socket new_connection(SockAddr addr, char *hostname,
        sk_proxy_close,
        sk_proxy_write,
        sk_proxy_write_oob,
+       sk_proxy_write_eof,
        sk_proxy_flush,
        sk_proxy_set_private_ptr,
        sk_proxy_get_private_ptr,
@@ -412,6 +427,7 @@ Socket new_connection(SockAddr addr, char *hostname,
 
        ret->error = NULL;
        ret->pending_flush = 0;
+       ret->pending_eof = 0;
        ret->freeze = 0;
 
        bufchain_init(&ret->pending_input_data);
diff --git a/proxy.h b/proxy.h
index 3df21c7..10a8c67 100644 (file)
--- a/proxy.h
+++ b/proxy.h
@@ -30,6 +30,7 @@ struct Socket_proxy_tag {
     bufchain pending_oob_output_data;
     int pending_flush;
     bufchain pending_input_data;
+    int pending_eof;
 
 #define PROXY_STATE_NEW    -1
 #define PROXY_STATE_ACTIVE  0
diff --git a/pscp.c b/pscp.c
index abdb8fd..833e46c 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -45,6 +45,7 @@ static int using_sftp = 0;
 static Backend *back;
 static void *backhandle;
 static Conf *conf;
+int sent_eof = FALSE;
 
 static void source(char *src);
 static void rsource(char *src);
@@ -214,6 +215,19 @@ int from_backend_untrusted(void *frontend_handle, const char *data, int len)
     assert(!"Unexpected call to from_backend_untrusted()");
     return 0; /* not reached */
 }
+int from_backend_eof(void *frontend)
+{
+    /*
+     * We expect to be the party deciding when to close the
+     * connection, so if we see EOF before we sent it ourselves, we
+     * should panic.
+     */
+    if (!sent_eof) {
+        connection_fatal(frontend,
+                         "Received unexpected end-of-file from server");
+    }
+    return FALSE;
+}
 static int ssh_scp_recv(unsigned char *buf, int len)
 {
     outptr = buf;
@@ -298,6 +312,7 @@ static void bump(char *fmt, ...)
     if (back != NULL && back->connected(backhandle)) {
        char ch;
        back->special(backhandle, TS_EOF);
+        sent_eof = TRUE;
        ssh_scp_recv((unsigned char *) &ch, 1);
     }
 
@@ -2314,6 +2329,7 @@ int psftp_main(int argc, char *argv[])
     if (back != NULL && back->connected(backhandle)) {
        char ch;
        back->special(backhandle, TS_EOF);
+        sent_eof = TRUE;
        ssh_scp_recv((unsigned char *) &ch, 1);
     }
     random_save_seed();
diff --git a/psftp.c b/psftp.c
index 5f91822..d7b7381 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -37,6 +37,7 @@ char *pwd, *homedir;
 static Backend *back;
 static void *backhandle;
 static Conf *conf;
+int sent_eof = FALSE;
 
 /* ----------------------------------------------------------------------
  * Higher-level helper functions used in commands.
@@ -980,6 +981,7 @@ int sftp_cmd_close(struct sftp_command *cmd)
     if (back != NULL && back->connected(backhandle)) {
        char ch;
        back->special(backhandle, TS_EOF);
+        sent_eof = TRUE;
        sftp_recvdata(&ch, 1);
     }
     do_sftp_cleanup();
@@ -2362,6 +2364,7 @@ void do_sftp_cleanup()
     char ch;
     if (back) {
        back->special(backhandle, TS_EOF);
+        sent_eof = TRUE;
        sftp_recvdata(&ch, 1);
        back->free(backhandle);
        sftp_cleanup_request();
@@ -2570,6 +2573,19 @@ int from_backend_untrusted(void *frontend_handle, const char *data, int len)
     assert(!"Unexpected call to from_backend_untrusted()");
     return 0; /* not reached */
 }
+int from_backend_eof(void *frontend)
+{
+    /*
+     * We expect to be the party deciding when to close the
+     * connection, so if we see EOF before we sent it ourselves, we
+     * should panic.
+     */
+    if (!sent_eof) {
+        connection_fatal(frontend,
+                         "Received unexpected end-of-file from SFTP server");
+    }
+    return FALSE;
+}
 int sftp_recvdata(char *buf, int len)
 {
     outptr = (unsigned char *) buf;
@@ -2952,6 +2968,7 @@ int psftp_main(int argc, char *argv[])
     if (back != NULL && back->connected(backhandle)) {
        char ch;
        back->special(backhandle, TS_EOF);
+        sent_eof = TRUE;
        sftp_recvdata(&ch, 1);
     }
     do_sftp_cleanup();
diff --git a/putty.h b/putty.h
index fad3486..79731b1 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -595,6 +595,11 @@ void ldisc_update(void *frontend, int echo, int edit);
 void update_specials_menu(void *frontend);
 int from_backend(void *frontend, int is_stderr, const char *data, int len);
 int from_backend_untrusted(void *frontend, const char *data, int len);
+/* Called when the back end wants to indicate that EOF has arrived on
+ * the server-to-client stream. Returns FALSE to indicate that we
+ * intend to keep the session open in the other direction, or TRUE to
+ * indicate that if they're closing so are we. */
+int from_backend_eof(void *frontend);
 void notify_remote_exit(void *frontend);
 /* Get a sensible value for a tty mode. NULL return = don't set.
  * Otherwise, returned value should be freed by caller. */
diff --git a/raw.c b/raw.c
index 6eb605d..5bf6356 100644 (file)
--- a/raw.c
+++ b/raw.c
@@ -23,6 +23,7 @@ typedef struct raw_backend_data {
     Socket s;
     int bufsize;
     void *frontend;
+    int sent_console_eof, sent_socket_eof;
 } *Raw;
 
 static void raw_size(void *handle, int width, int height);
@@ -49,21 +50,51 @@ static void raw_log(Plug plug, int type, SockAddr addr, int port,
     logevent(raw->frontend, msg);
 }
 
+static void raw_check_close(Raw raw)
+{
+    /*
+     * Called after we send EOF on either the socket or the console.
+     * Its job is to wind up the session once we have sent EOF on both.
+     */
+    if (raw->sent_console_eof && raw->sent_socket_eof) {
+        if (raw->s) {
+            sk_close(raw->s);
+            raw->s = NULL;
+            notify_remote_exit(raw->frontend);
+        }
+    }
+}
+
 static int raw_closing(Plug plug, const char *error_msg, int error_code,
                       int calling_back)
 {
     Raw raw = (Raw) plug;
 
-    if (raw->s) {
-        sk_close(raw->s);
-        raw->s = NULL;
-       notify_remote_exit(raw->frontend);
-    }
     if (error_msg) {
-       /* A socket error has occurred. */
-       logevent(raw->frontend, error_msg);
-       connection_fatal(raw->frontend, "%s", error_msg);
-    }                                 /* Otherwise, the remote side closed the connection normally. */
+        /* A socket error has occurred. */
+        if (raw->s) {
+            sk_close(raw->s);
+            raw->s = NULL;
+            notify_remote_exit(raw->frontend);
+        }
+        logevent(raw->frontend, error_msg);
+        connection_fatal(raw->frontend, "%s", error_msg);
+    } else {
+        /* Otherwise, the remote side closed the connection normally. */
+        if (!raw->sent_console_eof && from_backend_eof(raw->frontend)) {
+            /*
+             * The front end wants us to close the outgoing side of the
+             * connection as soon as we see EOF from the far end.
+             */
+            if (!raw->sent_socket_eof) {
+                if (raw->s)
+                    sk_write_eof(raw->s);
+                raw->sent_socket_eof= TRUE;
+            }
+        }
+        raw->sent_console_eof = TRUE;
+        raw_check_close(raw);
+    }
     return 0;
 }
 
@@ -109,6 +140,7 @@ static const char *raw_init(void *frontend_handle, void **backend_handle,
     raw->fn = &fn_table;
     raw->s = NULL;
     *backend_handle = raw;
+    raw->sent_console_eof = raw->sent_socket_eof = FALSE;
 
     raw->frontend = frontend_handle;
 
@@ -212,11 +244,17 @@ static void raw_size(void *handle, int width, int height)
 }
 
 /*
- * Send raw special codes.
+ * Send raw special codes. We only handle outgoing EOF here.
  */
 static void raw_special(void *handle, Telnet_Special code)
 {
-    /* Do nothing! */
+    Raw raw = (Raw) handle;
+    if (code == TS_EOF && raw->s) {
+        sk_write_eof(raw->s);
+        raw->sent_socket_eof= TRUE;
+        raw_check_close(raw);
+    }
+
     return;
 }
 
index 7cfe783..a79576e 100644 (file)
--- a/rlogin.c
+++ b/rlogin.c
@@ -62,6 +62,13 @@ static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
                          int calling_back)
 {
     Rlogin rlogin = (Rlogin) plug;
+
+    /*
+     * We don't implement independent EOF in each direction for Telnet
+     * connections; as soon as we get word that the remote side has
+     * sent us EOF, we wind up the whole connection.
+     */
+
     if (rlogin->s) {
         sk_close(rlogin->s);
         rlogin->s = NULL;
diff --git a/ssh.c b/ssh.c
index dafa148..291d58c 100644 (file)
--- 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");
diff --git a/ssh.h b/ssh.h
index 8d28847..60c54e4 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -9,8 +9,8 @@
 
 struct ssh_channel;
 
-extern void sshfwd_close(struct ssh_channel *c);
 extern int sshfwd_write(struct ssh_channel *c, char *, int);
+extern void sshfwd_write_eof(struct ssh_channel *c);
 extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
 
 /*
@@ -342,6 +342,7 @@ extern const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
 extern void pfd_close(Socket s);
 extern void pfd_terminate(void *sockdata);
 extern int pfd_send(Socket s, char *data, int len);
+extern void pfd_send_eof(Socket s);
 extern void pfd_confirm(Socket s);
 extern void pfd_unthrottle(Socket s);
 extern void pfd_override_throttle(Socket s, int enable);
@@ -397,6 +398,7 @@ extern const char *x11_init(Socket *, struct X11Display *, void *,
                            const char *, int, Conf *);
 extern void x11_close(Socket);
 extern int x11_send(Socket, char *, int);
+extern void x11_send_eof(Socket s);
 extern void x11_unthrottle(Socket s);
 extern void x11_override_throttle(Socket s, int enable);
 char *x11_display(const char *display);
index 7bdfedb..dc98a72 100644 (file)
--- a/telnet.c
+++ b/telnet.c
@@ -667,6 +667,12 @@ static int telnet_closing(Plug plug, const char *error_msg, int error_code,
 {
     Telnet telnet = (Telnet) plug;
 
+    /*
+     * We don't implement independent EOF in each direction for Telnet
+     * connections; as soon as we get word that the remote side has
+     * sent us EOF, we wind up the whole connection.
+     */
+
     if (telnet->s) {
         sk_close(telnet->s);
         telnet->s = NULL;
index 3f6f94b..a8be624 100644 (file)
@@ -210,6 +210,11 @@ int from_backend_untrusted(void *frontend, const char *data, int len)
     return term_data_untrusted(inst->term, data, len);
 }
 
+int from_backend_eof(void *frontend)
+{
+    return TRUE;   /* do respond to incoming EOF with outgoing */
+}
+
 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
     struct gui_data *inst = (struct gui_data *)p->frontend;
index d649e4c..45ea55d 100644 (file)
@@ -87,6 +87,8 @@ struct Socket_tag {
     int sending_oob;
     int oobpending;                   /* is there OOB data available to read? */
     int oobinline;
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+    int incomingeof;
     int pending_error;                /* in case send() returns error */
     int listener;
     int nodelay, keepalive;            /* for connect()-type sockets */
@@ -466,6 +468,7 @@ static void sk_tcp_flush(Socket s)
 static void sk_tcp_close(Socket s);
 static int sk_tcp_write(Socket s, const char *data, int len);
 static int sk_tcp_write_oob(Socket s, const char *data, int len);
+static void sk_tcp_write_eof(Socket s);
 static void sk_tcp_set_private_ptr(Socket s, void *ptr);
 static void *sk_tcp_get_private_ptr(Socket s);
 static void sk_tcp_set_frozen(Socket s, int is_frozen);
@@ -476,6 +479,7 @@ static struct socket_function_table tcp_fn_table = {
     sk_tcp_close,
     sk_tcp_write,
     sk_tcp_write_oob,
+    sk_tcp_write_eof,
     sk_tcp_flush,
     sk_tcp_set_private_ptr,
     sk_tcp_get_private_ptr,
@@ -502,6 +506,8 @@ Socket sk_register(OSSocket sockfd, Plug plug)
     ret->localhost_only = 0;          /* unused, but best init anyway */
     ret->pending_error = 0;
     ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
     ret->listener = 0;
     ret->parent = ret->child = NULL;
     ret->addr = NULL;
@@ -724,6 +730,8 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->pending_error = 0;
     ret->parent = ret->child = NULL;
     ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
     ret->listener = 0;
     ret->addr = addr;
     START_STEP(ret->addr, ret->step);
@@ -776,6 +784,8 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
     ret->pending_error = 0;
     ret->parent = ret->child = NULL;
     ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
     ret->listener = 1;
     ret->addr = NULL;
 
@@ -1053,6 +1063,20 @@ void try_send(Actual_Socket s)
            }
        }
     }
+
+    /*
+     * If we reach here, we've finished sending everything we might
+     * have needed to send. Send EOF, if we need to.
+     */
+    if (s->outgoingeof == EOF_PENDING) {
+        shutdown(s->s, SHUT_WR);
+        s->outgoingeof = EOF_SENT;
+    }
+
+    /*
+     * Also update the select status, because we don't need to select
+     * for writing any more.
+     */
     uxsel_tell(s);
 }
 
@@ -1060,6 +1084,8 @@ static int sk_tcp_write(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Add the data to the buffer list on the socket.
      */
@@ -1084,6 +1110,8 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Replace the buffer list on the socket with the data.
      */
@@ -1107,6 +1135,30 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
     return s->sending_oob;
 }
 
+static void sk_tcp_write_eof(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    assert(s->outgoingeof == EOF_NO);
+
+    /*
+     * Mark the socket as pending outgoing EOF.
+     */
+    s->outgoingeof = EOF_PENDING;
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    if (s->writable)
+       try_send(s);
+
+    /*
+     * Update the select() status to correctly reflect whether or
+     * not we should be selecting for write.
+     */
+    uxsel_tell(s);
+}
+
 static int net_select_result(int fd, int event)
 {
     int ret;
@@ -1237,6 +1289,8 @@ static int net_select_result(int fd, int event)
             if (err != 0)
                 return plug_closing(s->plug, strerror(err), err, 0);
        } else if (0 == ret) {
+            s->incomingeof = TRUE;     /* stop trying to read now */
+            uxsel_tell(s);
            return plug_closing(s->plug, NULL, 0, 0);
        } else {
             /*
@@ -1365,7 +1419,7 @@ static void uxsel_tell(Actual_Socket s)
     } else {
        if (!s->connected)
            rwx |= 2;                   /* write == connect */
-       if (s->connected && !s->frozen)
+       if (s->connected && !s->frozen && !s->incomingeof)
            rwx |= 1 | 4;               /* read, except */
        if (bufchain_size(&s->output_data))
            rwx |= 2;                   /* write */
index 4d36e50..aeb37f6 100644 (file)
@@ -383,6 +383,7 @@ void cleanup_termios(void)
 }
 
 bufchain stdout_data, stderr_data;
+enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
 
 int try_output(int is_stderr)
 {
@@ -391,23 +392,26 @@ int try_output(int is_stderr)
     void *senddata;
     int sendlen, ret, fl;
 
-    if (bufchain_size(chain) == 0)
-        return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
-
-    fl = fcntl(fd, F_GETFL);
-    if (fl != -1 && !(fl & O_NONBLOCK))
-       fcntl(fd, F_SETFL, fl | O_NONBLOCK);
-    do {
-       bufchain_prefix(chain, &senddata, &sendlen);
-       ret = write(fd, senddata, sendlen);
-       if (ret > 0)
-           bufchain_consume(chain, ret);
-    } while (ret == sendlen && bufchain_size(chain) != 0);
-    if (fl != -1 && !(fl & O_NONBLOCK))
-       fcntl(fd, F_SETFL, fl);
-    if (ret < 0 && errno != EAGAIN) {
-       perror(is_stderr ? "stderr: write" : "stdout: write");
-       exit(1);
+    if (bufchain_size(chain) > 0) {
+        fl = fcntl(fd, F_GETFL);
+        if (fl != -1 && !(fl & O_NONBLOCK))
+            fcntl(fd, F_SETFL, fl | O_NONBLOCK);
+        do {
+            bufchain_prefix(chain, &senddata, &sendlen);
+            ret = write(fd, senddata, sendlen);
+            if (ret > 0)
+                bufchain_consume(chain, ret);
+        } while (ret == sendlen && bufchain_size(chain) != 0);
+        if (fl != -1 && !(fl & O_NONBLOCK))
+            fcntl(fd, F_SETFL, fl);
+        if (ret < 0 && errno != EAGAIN) {
+            perror(is_stderr ? "stderr: write" : "stdout: write");
+            exit(1);
+        }
+    }
+    if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) {
+        close(STDOUT_FILENO);
+        outgoingeof = EOF_SENT;
     }
     return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
 }
@@ -419,6 +423,7 @@ int from_backend(void *frontend_handle, int is_stderr,
        bufchain_add(&stderr_data, data, len);
        return try_output(TRUE);
     } else {
+        assert(outgoingeof == EOF_NO);
        bufchain_add(&stdout_data, data, len);
        return try_output(FALSE);
     }
@@ -434,6 +439,14 @@ int from_backend_untrusted(void *frontend_handle, const char *data, int len)
     return 0; /* not reached */
 }
 
+int from_backend_eof(void *frontend_handle)
+{
+    assert(outgoingeof == EOF_NO);
+    outgoingeof = EOF_PENDING;
+    try_output(FALSE);
+    return FALSE;   /* do not respond to incoming EOF with outgoing */
+}
+
 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
     int ret;
@@ -599,6 +612,10 @@ int main(int argc, char **argv)
     default_protocol = PROT_SSH;
     default_port = 22;
 
+    bufchain_init(&stdout_data);
+    bufchain_init(&stderr_data);
+    outgoingeof = EOF_NO;
+
     flags = FLAG_STDERR | FLAG_STDERR_TTY;
 
     stderr_tty_init();
index b441b80..def8a40 100644 (file)
@@ -29,6 +29,7 @@ struct Socket_localproxy_tag {
 
     bufchain pending_output_data;
     bufchain pending_input_data;
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
 
     void *privptr;
 };
@@ -95,12 +96,14 @@ static void sk_localproxy_close (Socket s)
 {
     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
 
-    del234(localproxy_by_fromfd, ps);
-    del234(localproxy_by_tofd, ps);
+    if (ps->to_cmd >= 0) {
+        del234(localproxy_by_tofd, ps);
+        uxsel_del(ps->to_cmd);
+        close(ps->to_cmd);
+    }
 
-    uxsel_del(ps->to_cmd);
+    del234(localproxy_by_fromfd, ps);
     uxsel_del(ps->from_cmd);
-    close(ps->to_cmd);
     close(ps->from_cmd);
 
     sfree(ps);
@@ -129,6 +132,14 @@ static int localproxy_try_send(Local_Proxy_Socket ps)
        }
     }
 
+    if (ps->outgoingeof == EOF_PENDING) {
+        del234(localproxy_by_tofd, ps);
+        close(ps->to_cmd);
+        uxsel_del(ps->to_cmd);
+        ps->to_cmd = -1;
+        ps->outgoingeof = EOF_SENT;
+    }
+
     if (bufchain_size(&ps->pending_output_data) == 0)
        uxsel_del(ps->to_cmd);
     else
@@ -141,6 +152,8 @@ static int sk_localproxy_write (Socket s, const char *data, int len)
 {
     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
 
+    assert(ps->outgoingeof == EOF_NO);
+
     bufchain_add(&ps->pending_output_data, data, len);
 
     localproxy_try_send(ps);
@@ -157,6 +170,16 @@ static int sk_localproxy_write_oob (Socket s, const char *data, int len)
     return sk_localproxy_write(s, data, len);
 }
 
+static void sk_localproxy_write_eof (Socket s)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+    assert(ps->outgoingeof == EOF_NO);
+    ps->outgoingeof = EOF_PENDING;
+
+    localproxy_try_send(ps);
+}
+
 static void sk_localproxy_flush (Socket s)
 {
     /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
@@ -233,6 +256,7 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
        sk_localproxy_close,
        sk_localproxy_write,
        sk_localproxy_write_oob,
+       sk_localproxy_write_eof,
        sk_localproxy_flush,
        sk_localproxy_set_private_ptr,
        sk_localproxy_get_private_ptr,
@@ -252,6 +276,7 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
     ret->fn = &socket_fn_table;
     ret->plug = plug;
     ret->error = NULL;
+    ret->outgoingeof = EOF_NO;
 
     bufchain_init(&ret->pending_input_data);
     bufchain_init(&ret->pending_output_data);
index d558cc9..04661fd 100644 (file)
@@ -5612,6 +5612,11 @@ int from_backend_untrusted(void *frontend, const char *data, int len)
     return term_data_untrusted(term, data, len);
 }
 
+int from_backend_eof(void *frontend)
+{
+    return TRUE;   /* do respond to incoming EOF with outgoing */
+}
+
 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
     int ret;
index c0e8232..286f79a 100644 (file)
@@ -250,6 +250,7 @@ struct handle_output {
      * Data only ever read or written by the main thread.
      */
     bufchain queued_data;             /* data still waiting to be written */
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
 
     /*
      * Callback function called when the backlog in the bufchain
@@ -320,6 +321,11 @@ static void handle_try_output(struct handle_output *ctx)
        ctx->len = sendlen;
        SetEvent(ctx->ev_from_main);
        ctx->busy = TRUE;
+    } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 &&
+               ctx->outgoingeof == EOF_PENDING) {
+        CloseHandle(ctx->h);
+        ctx->h = INVALID_HANDLE_VALUE;
+        ctx->outgoingeof = EOF_SENT;
     }
 }
 
@@ -408,6 +414,7 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
     h->u.o.done = FALSE;
     h->u.o.privdata = privdata;
     bufchain_init(&h->u.o.queued_data);
+    h->u.o.outgoingeof = EOF_NO;
     h->u.o.sentdata = sentdata;
     h->u.o.flags = flags;
 
@@ -424,11 +431,28 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
 int handle_write(struct handle *h, const void *data, int len)
 {
     assert(h->output);
+    assert(h->u.o.outgoingeof == EOF_NO);
     bufchain_add(&h->u.o.queued_data, data, len);
     handle_try_output(&h->u.o);
     return bufchain_size(&h->u.o.queued_data);
 }
 
+void handle_write_eof(struct handle *h)
+{
+    /*
+     * This function is called when we want to proactively send an
+     * end-of-file notification on the handle. We can only do this by
+     * actually closing the handle - so never call this on a
+     * bidirectional handle if we're still interested in its incoming
+     * direction!
+     */
+    assert(h->output);
+    if (!h->u.o.outgoingeof == EOF_NO) {
+        h->u.o.outgoingeof = EOF_PENDING;
+        handle_try_output(&h->u.o);
+    }
+}
+
 HANDLE *handle_get_events(int *nevents)
 {
     HANDLE *ret;
index 9f5c993..84b239c 100644 (file)
@@ -64,6 +64,7 @@ struct Socket_tag {
     char oobdata[1];
     int sending_oob;
     int oobinline, nodelay, keepalive, privport;
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
     SockAddr addr;
     SockAddrStep step;
     int port;
@@ -167,6 +168,7 @@ DECL_WINDOWS_FUNCTION(static, int, setsockopt,
 DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int));
 DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
 DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
                      (SOCKET, long, u_long FAR *));
 DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
@@ -291,6 +293,7 @@ void sk_init(void)
     GET_WINDOWS_FUNCTION(winsock_module, socket);
     GET_WINDOWS_FUNCTION(winsock_module, listen);
     GET_WINDOWS_FUNCTION(winsock_module, send);
+    GET_WINDOWS_FUNCTION(winsock_module, shutdown);
     GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
     GET_WINDOWS_FUNCTION(winsock_module, accept);
     GET_WINDOWS_FUNCTION(winsock_module, recv);
@@ -745,6 +748,7 @@ static void sk_tcp_flush(Socket s)
 static void sk_tcp_close(Socket s);
 static int sk_tcp_write(Socket s, const char *data, int len);
 static int sk_tcp_write_oob(Socket s, const char *data, int len);
+static void sk_tcp_write_eof(Socket s);
 static void sk_tcp_set_private_ptr(Socket s, void *ptr);
 static void *sk_tcp_get_private_ptr(Socket s);
 static void sk_tcp_set_frozen(Socket s, int is_frozen);
@@ -759,6 +763,7 @@ Socket sk_register(void *sock, Plug plug)
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -780,6 +785,7 @@ Socket sk_register(void *sock, Plug plug)
     bufchain_init(&ret->output_data);
     ret->writable = 1;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 1;
     ret->frozen_readable = 0;
     ret->localhost_only = 0;          /* unused, but best init anyway */
@@ -1007,6 +1013,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -1028,6 +1035,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     ret->connected = 0;                       /* to start with */
     ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 0;
     ret->frozen_readable = 0;
     ret->localhost_only = 0;          /* unused, but best init anyway */
@@ -1058,6 +1066,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
        sk_tcp_close,
        sk_tcp_write,
        sk_tcp_write_oob,
+       sk_tcp_write_eof,
        sk_tcp_flush,
        sk_tcp_set_private_ptr,
        sk_tcp_get_private_ptr,
@@ -1089,6 +1098,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
     bufchain_init(&ret->output_data);
     ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
+    ret->outgoingeof = EOF_NO;
     ret->frozen = 0;
     ret->frozen_readable = 0;
     ret->localhost_only = local_host_only;
@@ -1325,12 +1335,23 @@ void try_send(Actual_Socket s)
            }
        }
     }
+
+    /*
+     * If we reach here, we've finished sending everything we might
+     * have needed to send. Send EOF, if we need to.
+     */
+    if (s->outgoingeof == EOF_PENDING) {
+        p_shutdown(s->s, SD_SEND);
+        s->outgoingeof = EOF_SENT;
+    }
 }
 
 static int sk_tcp_write(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Add the data to the buffer list on the socket.
      */
@@ -1349,6 +1370,8 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Replace the buffer list on the socket with the data.
      */
@@ -1366,6 +1389,24 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
     return s->sending_oob;
 }
 
+static void sk_tcp_write_eof(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    assert(s->outgoingeof == EOF_NO);
+
+    /*
+     * Mark the socket as pending outgoing EOF.
+     */
+    s->outgoingeof = EOF_PENDING;
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    if (s->writable)
+       try_send(s);
+}
+
 int select_result(WPARAM wParam, LPARAM lParam)
 {
     int ret, open;
index 3fe03af..5806335 100644 (file)
@@ -130,6 +130,12 @@ int from_backend_untrusted(void *frontend_handle, const char *data, int len)
     return 0; /* not reached */
 }
 
+int from_backend_eof(void *frontend_handle)
+{
+    handle_write_eof(stdout_handle);
+    return FALSE;   /* do not respond to incoming EOF with outgoing */
+}
+
 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
 {
     int ret;
index b1d3f6e..8be22ea 100644 (file)
@@ -87,6 +87,13 @@ static int sk_localproxy_write_oob(Socket s, const char *data, int len)
     return sk_localproxy_write(s, data, len);
 }
 
+static void sk_localproxy_write_eof(Socket s)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+    handle_write_eof(ps->to_cmd_h);
+}
+
 static void sk_localproxy_flush(Socket s)
 {
     /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
@@ -132,6 +139,7 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
        sk_localproxy_close,
        sk_localproxy_write,
        sk_localproxy_write_oob,
+       sk_localproxy_write_eof,
        sk_localproxy_flush,
        sk_localproxy_set_private_ptr,
        sk_localproxy_get_private_ptr,
index 2b70ddc..8738ccf 100644 (file)
@@ -489,6 +489,7 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
 struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
                                 void *privdata, int flags);
 int handle_write(struct handle *h, const void *data, int len);
+void handle_write_eof(struct handle *h);
 HANDLE *handle_get_events(int *nevents);
 void handle_free(struct handle *h);
 void handle_got_event(HANDLE event);
index 9082471..ba5bbf9 100644 (file)
--- a/x11fwd.c
+++ b/x11fwd.c
@@ -507,9 +507,12 @@ static int x11_closing(Plug plug, const char *error_msg, int error_code,
      * We have no way to communicate down the forwarded connection,
      * so if an error occurred on the socket, we just ignore it
      * and treat it like a proper close.
+     *
+     * FIXME: except we could initiate a full close here instead of
+     * just an outgoing EOF? ssh.c currently has no API for that, but
+     * it could.
      */
-    sshfwd_close(pr->c);
-    x11_close(pr->s);
+    sshfwd_write_eof(pr->c);
     return 1;
 }
 
@@ -721,8 +724,7 @@ int x11_send(Socket s, char *data, int len)
            memset(reply + 8, 0, msgsize);
            memcpy(reply + 8, message, msglen);
            sshfwd_write(pr->c, (char *)reply, 8 + msgsize);
-           sshfwd_close(pr->c);
-           x11_close(s);
+           sshfwd_write_eof(pr->c);
            sfree(reply);
            sfree(message);
            return 0;
@@ -787,3 +789,8 @@ int x11_send(Socket s, char *data, int len)
 
     return sk_write(s, data, len);
 }
+
+void x11_send_eof(Socket s)
+{
+    sk_write_eof(s);
+}