More fixes to stdout and stderr. When the backlog on either clears, call
[sgt/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 38f1495..fb24f43 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -533,6 +533,14 @@ enum {                                    /* channel types */
 };
 
 /*
+ * little structure to keep track of outstanding WINDOW_ADJUSTs
+ */
+struct winadj {
+    struct winadj *next;
+    unsigned size;
+};
+
+/*
  * 2-3-4 tree storing channels.
  */
 struct ssh_channel {
@@ -561,6 +569,18 @@ struct ssh_channel {
            unsigned remwindow, remmaxpkt;
            /* locwindow is signed so we can cope with excess data. */
            int locwindow, locmaxwin;
+           /*
+            * remlocwin is the amount of local window that we think
+            * the remote end had available to it after it sent the
+            * last data packet or window adjust ack.
+            */
+           int remlocwin;
+           /*
+            * These store the list of window adjusts that haven't
+            * been acked.
+            */
+           struct winadj *winadj_head, *winadj_tail;
+           enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
        } v2;
     } v;
     union {
@@ -6203,7 +6223,51 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
      */
     if (newwin / 2 >= c->v.v2.locwindow) {
        struct Packet *pktout;
+       struct winadj *wa;
 
+       /*
+        * In order to keep track of how much window the client
+        * actually has available, we'd like it to acknowledge each
+        * WINDOW_ADJUST.  We can't do that directly, so we accompany
+        * it with a CHANNEL_REQUEST that has to be acknowledged.
+        *
+        * This is only necessary if we're opening the window wide.
+        * If we're not, then throughput is being constrained by
+        * something other than the maximum window size anyway.
+        *
+        * We also only send this if the main channel has finished its
+        * initial CHANNEL_REQUESTs and installed the default
+        * CHANNEL_FAILURE handler, so as not to risk giving it
+        * unexpected CHANNEL_FAILUREs.
+        */
+       if (newwin == c->v.v2.locmaxwin &&
+           ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
+           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);
+
+           /*
+            * CHANNEL_FAILURE doesn't come with any indication of
+            * what message caused it, so we have to keep track of the
+            * outstanding CHANNEL_REQUESTs ourselves.
+            */
+           wa = snew(struct winadj);
+           wa->size = newwin - c->v.v2.locwindow;
+           wa->next = NULL;
+           if (!c->v.v2.winadj_head)
+               c->v.v2.winadj_head = wa;
+           else
+               c->v.v2.winadj_tail->next = wa;
+           c->v.v2.winadj_tail = wa;
+           if (c->v.v2.throttle_state != UNTHROTTLED)
+               c->v.v2.throttle_state = UNTHROTTLING;
+       } else {
+           /* Pretend the WINDOW_ADJUST was acked immediately. */
+           c->v.v2.remlocwin = newwin;
+           c->v.v2.throttle_state = THROTTLED;
+       }
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
        ssh2_pkt_adduint32(pktout, c->remoteid);
        ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow);
@@ -6212,12 +6276,94 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
     }
 }
 
+/*
+ * Find the channel associated with a message.  If there's no channel,
+ * or it's not properly open, make a noise about it and return NULL.
+ */
+static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+{
+    unsigned localid = ssh_pkt_getuint32(pktin);
+    struct ssh_channel *c;
+
+    c = find234(ssh->channels, &localid, ssh_channelfind);
+    if (!c ||
+       (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+        pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+       char *buf = dupprintf("Received %s for %s channel %u",
+                             ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+                                           pktin->type),
+                             c ? "half-open" : "nonexistent", localid);
+       ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+       sfree(buf);
+       return NULL;
+    }
+    return c;
+}
+
+static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
+{
+    /*
+     * This should never get called.  All channel requests are either
+     * sent with want_reply false or are sent before this handler gets
+     * installed.
+     */
+    struct ssh_channel *c;
+    struct winadj *wa;
+
+    c = ssh2_channel_msg(ssh, pktin);
+    if (!c)
+       return;
+    wa = c->v.v2.winadj_head;
+    if (wa)
+       ssh_disconnect(ssh, NULL, "Received SSH_MSG_CHANNEL_SUCCESS for "
+                      "\"winadj@putty.projects.tartarus.org\"",
+                      SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+    else
+       ssh_disconnect(ssh, NULL,
+                      "Received unsolicited SSH_MSG_CHANNEL_SUCCESS",
+                      SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+}
+
+static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
+{
+    /*
+     * The only time this should get called is for "winadj@putty"
+     * messages sent above.  All other channel requests are either
+     * sent with want_reply false or are sent before this handler gets
+     * installed.
+     */
+    struct ssh_channel *c;
+    struct winadj *wa;
+
+    c = ssh2_channel_msg(ssh, pktin);
+    if (!c)
+       return;
+    wa = c->v.v2.winadj_head;
+    if (!wa) {
+       ssh_disconnect(ssh, NULL,
+                      "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
+                      SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+       return;
+    }
+    c->v.v2.winadj_head = wa->next;
+    c->v.v2.remlocwin += wa->size;
+    sfree(wa);
+    /*
+     * winadj messages are only sent when the window is fully open, so
+     * if we get an ack of one, we know any pending unthrottle is
+     * complete.
+     */
+    if (c->v.v2.throttle_state == UNTHROTTLING)
+       c->v.v2.throttle_state = UNTHROTTLED;
+}
+
 static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
 {
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
-    c = find234(ssh->channels, &i, ssh_channelfind);
-    if (c && !c->closes) {
+    c = ssh2_channel_msg(ssh, pktin);
+    if (!c)
+       return;
+    if (!c->closes) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
        ssh2_try_send_and_unthrottle(c);
     }
@@ -6227,11 +6373,10 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
 {
     char *data;
     int length;
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
-    c = find234(ssh->channels, &i, ssh_channelfind);
+    c = ssh2_channel_msg(ssh, pktin);
     if (!c)
-       return;                        /* nonexistent channel */
+       return;
     if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
        ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
        return;                        /* extended but not stderr */
@@ -6239,6 +6384,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
     if (data) {
        int bufsize = 0;
        c->v.v2.locwindow -= length;
+       c->v.v2.remlocwin -= length;
        switch (c->type) {
          case CHAN_MAINSESSION:
            bufsize =
@@ -6296,6 +6442,14 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
            break;
        }
        /*
+        * If it looks like the remote end hit the end of its window,
+        * and we didn't want it to do that, think about using a
+        * larger window.
+        */
+       if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
+           c->v.v2.locmaxwin < 0x40000000)
+           c->v.v2.locmaxwin += OUR_V2_WINSIZE;
+       /*
         * If we are not buffering too much data,
         * enlarge the window again at the remote side.
         * If we are buffering too much, we may still
@@ -6309,12 +6463,11 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
 
 static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
 {
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
 
-    c = find234(ssh->channels, &i, ssh_channelfind);
+    c = ssh2_channel_msg(ssh, pktin);
     if (!c)
-       return;                        /* nonexistent channel */
+       return;
 
     if (c->type == CHAN_X11) {
        /*
@@ -6333,16 +6486,12 @@ static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
 
 static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
 {
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
     struct Packet *pktout;
 
-    c = find234(ssh->channels, &i, ssh_channelfind);
-    if (!c || c->halfopen) {
-       bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
-                c ? "half-open" : "nonexistent", i));
+    c = ssh2_channel_msg(ssh, pktin);
+    if (!c)
        return;
-    }
     /* Do pre-close processing on the channel. */
     switch (c->type) {
       case CHAN_MAINSESSION:
@@ -6395,13 +6544,12 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
 
 static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
 {
-    unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
     struct Packet *pktout;
 
-    c = find234(ssh->channels, &i, ssh_channelfind);
+    c = ssh2_channel_msg(ssh, pktin);
     if (!c)
-       return;                        /* nonexistent channel */
+       return;
     if (c->type != CHAN_SOCKDATA_DORMANT)
        return;                        /* dunno why they're confirming this */
     c->remoteid = ssh_pkt_getuint32(pktin);
@@ -6433,14 +6581,13 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
            "Unknown channel type",
            "Resource shortage",
     };
-    unsigned i = ssh_pkt_getuint32(pktin);
     unsigned reason_code;
     char *reason_string;
     int reason_length;
     struct ssh_channel *c;
-    c = find234(ssh->channels, &i, ssh_channelfind);
+    c = ssh2_channel_msg(ssh, pktin);
     if (!c)
-       return;                        /* nonexistent channel */
+       return;
     if (c->type != CHAN_SOCKDATA_DORMANT)
        return;                        /* dunno why they're failing this */
 
@@ -6459,31 +6606,19 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
 
 static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
 {
-    unsigned localid;
     char *type;
     int typelen, want_reply;
     int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
     struct ssh_channel *c;
     struct Packet *pktout;
 
-    localid = ssh_pkt_getuint32(pktin);
+    c = ssh2_channel_msg(ssh, pktin);
+    if (!c)
+       return;
     ssh_pkt_getstring(pktin, &type, &typelen);
     want_reply = ssh2_pkt_getbool(pktin);
 
     /*
-     * First, check that the channel exists. Otherwise,
-     * we can instantly disconnect with a rude message.
-     */
-    c = find234(ssh->channels, &localid, ssh_channelfind);
-    if (!c) {
-       char *buf = dupprintf("Received channel request for nonexistent"
-                             " channel %d", localid);
-       ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
-       sfree(buf);
-       return;
-    }
-
-    /*
      * Having got the channel number, we now look at
      * the request type string to see if it's something
      * we recognise.
@@ -6773,6 +6908,9 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
        c->v.v2.remwindow = winsize;
        c->v.v2.remmaxpkt = pktsize;
+       c->v.v2.remlocwin = OUR_V2_WINSIZE;
+       c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+       c->v.v2.throttle_state = UNTHROTTLED;
        bufchain_init(&c->v.v2.outbuffer);
        add234(ssh->channels, c);
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
@@ -7982,7 +8120,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addstring(s->pktout, "direct-tcpip");
        ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
        ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
+           ssh->mainchan->v.v2.remlocwin =
            ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+       ssh->mainchan->v.v2.winadj_head = NULL;
+       ssh->mainchan->v.v2.winadj_tail = NULL;
+       ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
        ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
        ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT);      /* our max pkt size */
        ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host);
@@ -8025,7 +8167,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addstring(s->pktout, "session");
        ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
        ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
+           ssh->mainchan->v.v2.remlocwin =
            ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+       ssh->mainchan->v.v2.winadj_head = NULL;
+       ssh->mainchan->v.v2.winadj_tail = NULL;
+       ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
        ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
        ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT);    /* our max pkt size */
        ssh2_pkt_send(ssh, s->pktout);
@@ -8339,6 +8485,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh_special(ssh, TS_EOF);
 
     /*
+     * All the initial channel requests are done, so install the default
+     * failure handler.
+     */
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success;
+    ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
+
+    /*
      * Transfer data!
      */
     if (ssh->ldisc)
@@ -9118,6 +9271,9 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
        ssh2_pkt_addstring(pktout, "direct-tcpip");
        ssh2_pkt_adduint32(pktout, c->localid);
        c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
+       c->v.v2.remlocwin = OUR_V2_WINSIZE;
+       c->v.v2.winadj_head = c->v.v2.winadj_head = NULL;
+       c->v.v2.throttle_state = UNTHROTTLED;
        ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
        ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT);      /* our max pkt size */
        ssh2_pkt_addstring(pktout, hostname);