Don't close SSH-2 channels with outstanding channel requests on local error.
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 3ab9725..b773e85 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -439,6 +439,8 @@ enum {
 #define crState(t)     crStateP(t, ssh->t)
 #define crFinish(z)    } *crLine = 0; return (z); }
 #define crFinishV      } *crLine = 0; return; }
+#define crFinishFree(z, s)     } *crLine = 0; sfree(s); return (z); }
+#define crFinishFreeV(s)       } *crLine = 0; sfree(s); return; }
 #define crReturn(z)    \
        do {\
            *crLine =__LINE__; return (z); case __LINE__:;\
@@ -4244,6 +4246,7 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
     if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
         return;              /* can't send EOF: pending outgoing data */
 
+    c->pending_eof = FALSE;            /* we're about to send it */
     if (ssh->version == 1) {
         send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                     PKT_END);
@@ -4254,17 +4257,8 @@ static void ssh_channel_try_eof(struct ssh_channel *c)
         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;
-        }
+       ssh2_channel_check_close(c);
     }
-    c->pending_eof = FALSE;            /* we've sent it now */
 }
 
 void sshfwd_write_eof(struct ssh_channel *c)
@@ -4284,25 +4278,19 @@ void sshfwd_write_eof(struct ssh_channel *c)
 void sshfwd_unclean_close(struct ssh_channel *c)
 {
     Ssh ssh = c->ssh;
-    struct Packet *pktout;
 
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (!(c->closes & CLOSES_SENT_CLOSE)) {
-        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-        ssh2_pkt_adduint32(pktout, c->remoteid);
-        ssh2_pkt_send(ssh, pktout);
-        c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
-    }
-
     switch (c->type) {
       case CHAN_X11:
         x11_close(c->u.x11.s);
+        logevent("Forwarded X11 connection terminated due to local error");
         break;
       case CHAN_SOCKDATA:
       case CHAN_SOCKDATA_DORMANT:
         pfd_close(c->u.pfd.s);
+        logevent("Forwarded port closed due to local error");
         break;
     }
     c->type = CHAN_ZOMBIE;
@@ -6599,6 +6587,7 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
     struct outstanding_channel_request *ocr =
        snew(struct outstanding_channel_request);
 
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
     ocr->handler = handler;
     ocr->ctx = ctx;
     ocr->next = NULL;
@@ -6610,6 +6599,33 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
 }
 
 /*
+ * Construct the common parts of a CHANNEL_REQUEST.  If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives.  The returned packet is ready to have any
+ * request-specific data added and be sent.  Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin.
+ * If pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed) and the
+ * handler should free any storage it's holding.
+ */
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+                                       cchandler_fn_t handler, void *ctx)
+{
+    struct Packet *pktout;
+
+    assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+    pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+    ssh2_pkt_adduint32(pktout, c->remoteid);
+    ssh2_pkt_addstring(pktout, type);
+    ssh2_pkt_addbool(pktout, handler != NULL);
+    if (handler != NULL)
+       ssh2_queue_chanreq_handler(c, handler, ctx);
+    return pktout;
+}
+
+/*
  * Potentially enlarge the window on an SSH-2 channel.
  */
 static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *,
@@ -6658,15 +6674,12 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
         */
        if (newwin == c->v.v2.locmaxwin &&
             !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, c->remoteid);
-           ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
-           ssh2_pkt_addbool(pktout, TRUE);
-           ssh2_pkt_send(ssh, pktout);
-
            up = snew(unsigned);
            *up = newwin - c->v.v2.locwindow;
-           ssh2_queue_chanreq_handler(c, ssh2_handle_winadj_response, up);
+           pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+                                      ssh2_handle_winadj_response, up);
+           ssh2_pkt_send(ssh, pktout);
+
            if (c->v.v2.throttle_state != UNTHROTTLED)
                c->v.v2.throttle_state = UNTHROTTLING;
        } else {
@@ -6893,8 +6906,10 @@ static void ssh_channel_destroy(struct ssh_channel *c)
     }
 
     del234(ssh->channels, c);
-    if (ssh->version == 2)
+    if (ssh->version == 2) {
         bufchain_clear(&c->v.v2.outbuffer);
+       assert(c->v.v2.chanreq_head == NULL);
+    }
     sfree(c);
 
     /*
@@ -6925,21 +6940,24 @@ 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) && !c->v.v2.chanreq_head) {
+    if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) ||
+        c->type == CHAN_ZOMBIE) &&
+       !c->v.v2.chanreq_head &&
+       !(c->closes & CLOSES_SENT_CLOSE)) {
         /*
-         * We have both sent and received EOF, and we have no
-         * outstanding winadj channel requests, which means the
-         * channel is in final wind-up. But we haven't sent CLOSE, so
-         * let's do so now.
+         * We have both sent and received EOF (or the channel is a
+         * zombie), and we have no outstanding channel requests, which
+         * means the channel is in final wind-up. But we haven't sent
+         * CLOSE, so let's do so now.
          */
        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->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
     }
 
     if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+       assert(c->v.v2.chanreq_head == NULL);
         /*
          * We have both sent and received CLOSE, which means we're
          * completely done with the channel.
@@ -7447,7 +7465,6 @@ static void ssh2_send_ttymode(void *data, char *mode, char *val)
     ssh2_pkt_adduint32(pktout, arg);
 }
 
-static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin);
 static void ssh2_maybe_setup_x11(struct ssh_channel *c, struct Packet *pktin,
                                 void *ctx)
 {
@@ -7467,10 +7484,8 @@ static void ssh2_maybe_setup_x11(struct ssh_channel *c, struct Packet *pktin,
        (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
                                          conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
        logevent("Requesting X11 forwarding");
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(pktout, "x11-req");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
+       pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req",
+                                  ssh2_maybe_setup_x11, s);
        ssh2_pkt_addbool(pktout, 0);           /* many connections */
        ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthprotoname);
        /*
@@ -7486,25 +7501,17 @@ static void ssh2_maybe_setup_x11(struct ssh_channel *c, struct Packet *pktin,
        ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
        ssh2_pkt_send(ssh, pktout);
 
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_x11, s);
-
        crWaitUntilV(pktin);
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to X11 forwarding request:"
-                        " packet type %d", pktin->type));
-               sfree(s);
-               crStopV;
-           }
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
+       if (pktin) {
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+               logevent("X11 forwarding enabled");
+               ssh->X11_fwd_enabled = TRUE;
+           } else
+               logevent("X11 forwarding refused");
        }
     }
-    sfree(s);
-    crFinishV;
+    crFinishFreeV(s);
 }
 
 static void ssh2_maybe_setup_agent(struct ssh_channel *c, struct Packet *pktin,
@@ -7521,30 +7528,21 @@ static void ssh2_maybe_setup_agent(struct ssh_channel *c, struct Packet *pktin,
 
     if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
        logevent("Requesting OpenSSH-style agent forwarding");
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(pktout, "auth-agent-req@openssh.com");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
+       pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com",
+                                  ssh2_maybe_setup_agent, s);
        ssh2_pkt_send(ssh, pktout);
 
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_agent, s);
-
        crWaitUntilV(pktin);
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to agent forwarding request:"
-                        " packet type %d", pktin->type));
-               crStopV;
-           }
-           logevent("Agent forwarding refused");
-       } else {
-           logevent("Agent forwarding enabled");
-           ssh->agentfwd_enabled = TRUE;
+       if (pktin) {
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+               logevent("Agent forwarding enabled");
+               ssh->agentfwd_enabled = TRUE;
+           } else
+               logevent("Agent forwarding refused");
        }
     }
-    sfree(s);
-    crFinishV;
+    crFinishFreeV(s);
 }
 
 static void ssh2_maybe_setup_pty(struct ssh_channel *c, struct Packet *pktin,
@@ -7565,10 +7563,8 @@ static void ssh2_maybe_setup_pty(struct ssh_channel *c, struct Packet *pktin,
         ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
        sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
        /* Build the pty request. */
-       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);    /* recipient channel */
-       ssh2_pkt_addstring(pktout, "pty-req");
-       ssh2_pkt_addbool(pktout, 1);           /* want reply */
+       pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req",
+                                  ssh2_maybe_setup_pty, s);
        ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
        ssh2_pkt_adduint32(pktout, ssh->term_width);
        ssh2_pkt_adduint32(pktout, ssh->term_height);
@@ -7584,28 +7580,22 @@ static void ssh2_maybe_setup_pty(struct ssh_channel *c, struct Packet *pktin,
        ssh2_pkt_send(ssh, pktout);
        ssh->state = SSH_STATE_INTERMED;
 
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_maybe_setup_pty, s);
-
        crWaitUntilV(pktin);
 
-       if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-           if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-               bombout(("Unexpected response to pty request:"
-                        " packet type %d", pktin->type));
-               crStopV;
+       if (pktin) {
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+               logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+                         ssh->ospeed, ssh->ispeed);
+               ssh->got_pty = TRUE;
+           } else {
+               c_write_str(ssh, "Server refused to allocate pty\r\n");
+               ssh->editing = ssh->echoing = 1;
            }
-           c_write_str(ssh, "Server refused to allocate pty\r\n");
-           ssh->editing = ssh->echoing = 1;
-       } else {
-           logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
-                     ssh->ospeed, ssh->ispeed);
-            ssh->got_pty = TRUE;
        }
     } else {
        ssh->editing = ssh->echoing = 1;
     }
-    sfree(s);
-    crFinishV;
+    crFinishFreeV(s);
 }
 
 static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
@@ -7634,14 +7624,10 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
        for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
             val != NULL;
             val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(pktout, "env");
-           ssh2_pkt_addbool(pktout, 1);               /* want reply */
+           pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
            ssh2_pkt_addstring(pktout, key);
            ssh2_pkt_addstring(pktout, val);
            ssh2_pkt_send(ssh, pktout);
-           ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_setup_env, s);
 
            s->num_env++;
        }
@@ -7655,17 +7641,9 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
 
        while (s->env_left > 0) {
            crWaitUntilV(pktin);
-
-           if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
-               if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
-                   bombout(("Unexpected response to environment request:"
-                            " packet type %d", pktin->type));
-                   crStopV;
-               }
-           } else {
+           if (!pktin) goto out;
+           if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
                s->env_ok++;
-           }
-
            s->env_left--;
        }
 
@@ -7680,8 +7658,8 @@ static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
            c_write_str(ssh, "Server refused to set all environment variables\r\n");
        }
     }
-    sfree(s);
-    crFinishV;
+  out:;
+    crFinishFreeV(s);
 }
 
 /*
@@ -9244,10 +9222,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
         * this one, so it's safe for it to advertise a very large
         * window and leave the flow control to TCP.
         */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
-       ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
-       ssh2_pkt_addbool(s->pktout, 0); /* no reply */
+       s->pktout = ssh2_chanreq_init(ssh->mainchan,
+                                     "simple@putty.projects.tartarus.org",
+                                     NULL, NULL);
        ssh2_pkt_send(ssh, s->pktout);
     }
 
@@ -9298,22 +9275,19 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
        }
 
-       s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-       ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
        if (subsys) {
-           ssh2_pkt_addstring(s->pktout, "subsystem");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+                                         ssh2_response_authconn, NULL);
            ssh2_pkt_addstring(s->pktout, cmd);
        } else if (*cmd) {
-           ssh2_pkt_addstring(s->pktout, "exec");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+                                         ssh2_response_authconn, NULL);
            ssh2_pkt_addstring(s->pktout, cmd);
        } else {
-           ssh2_pkt_addstring(s->pktout, "shell");
-           ssh2_pkt_addbool(s->pktout, 1);            /* want reply */
+           s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+                                         ssh2_response_authconn, NULL);
        }
        ssh2_pkt_send(ssh, s->pktout);
-       ssh2_queue_chanreq_handler(ssh->mainchan, ssh2_response_authconn, NULL);
 
        crWaitUntilV(pktin);
 
@@ -9754,6 +9728,17 @@ static void ssh_free(void *handle)
                    pfd_close(c->u.pfd.s);
                break;
            }
+           if (ssh->version == 2) {
+               struct outstanding_channel_request *ocr, *nocr;
+               ocr = c->v.v2.chanreq_head;
+               while (ocr) {
+                   ocr->handler(c, NULL, ocr->ctx);
+                   nocr = ocr->next;
+                   sfree(ocr);
+                   ocr = nocr;
+               }
+               bufchain_clear(&c->v.v2.outbuffer);
+           }
            sfree(c);
        }
        freetree234(ssh->channels);
@@ -9939,10 +9924,8 @@ static void ssh_size(void *handle, int width, int height)
                            PKT_INT, ssh->term_width,
                            PKT_INT, 0, PKT_INT, 0, PKT_END);
            } else if (ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "window-change");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "window-change",
+                                          NULL, NULL);
                ssh2_pkt_adduint32(pktout, ssh->term_width);
                ssh2_pkt_adduint32(pktout, ssh->term_height);
                ssh2_pkt_adduint32(pktout, 0);
@@ -10078,10 +10061,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (ssh->version == 1) {
            logevent("Unable to send BREAK signal in SSH-1");
        } else if (ssh->mainchan) {
-           pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-           ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-           ssh2_pkt_addstring(pktout, "break");
-           ssh2_pkt_addbool(pktout, 0);
+           pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL);
            ssh2_pkt_adduint32(pktout, 0);   /* default break length */
            ssh2_pkt_send(ssh, pktout);
        }
@@ -10106,10 +10086,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        if (signame) {
            /* It's a signal. */
            if (ssh->version == 2 && ssh->mainchan) {
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-               ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
-               ssh2_pkt_addstring(pktout, "signal");
-               ssh2_pkt_addbool(pktout, 0);
+               pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL);
                ssh2_pkt_addstring(pktout, signame);
                ssh2_pkt_send(ssh, pktout);
                logeventf(ssh, "Sent signal SIG%s", signame);