Use a single sftp_senddata() to send each SFTP packet, rather than
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index a211213..f39894a 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -196,6 +196,7 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_PK_SESSIONID                   128
 #define BUG_SSH2_MAXPKT                                256
 #define BUG_CHOKES_ON_SSH2_IGNORE               512
+#define BUG_CHOKES_ON_WINADJ                   1024
 
 /*
  * Codes for terminal modes.
@@ -517,9 +518,6 @@ static void ssh_channel_destroy(struct ssh_channel *c);
 #define OUR_V2_MAXPKT 0x4000UL
 #define OUR_V2_PACKETLIMIT 0x9000UL
 
-/* Maximum length of passwords/passphrases (arbitrary) */
-#define SSH_MAX_PASSWORD_LEN 100
-
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 
 const static struct ssh_mac *macs[] = {
@@ -561,7 +559,15 @@ enum {                                    /* channel types */
     CHAN_X11,
     CHAN_AGENT,
     CHAN_SOCKDATA,
-    CHAN_SOCKDATA_DORMANT             /* one the remote hasn't confirmed */
+    CHAN_SOCKDATA_DORMANT,            /* one the remote hasn't confirmed */
+    /*
+     * CHAN_ZOMBIE is used to indicate a channel for which we've
+     * already destroyed the local data source: for instance, if a
+     * forwarded port experiences a socket error on the local side, we
+     * immediately destroy its local socket and turn the SSH channel
+     * into CHAN_ZOMBIE.
+     */
+    CHAN_ZOMBIE
 };
 
 /*
@@ -2575,6 +2581,15 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
        ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
        logevent("We believe remote version has SSH-2 ignore bug");
     }
+
+    if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) {
+       /*
+        * Servers that don't support our winadj request for one
+        * reason or another. Currently, none detected automatically.
+        */
+       ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+       logevent("We believe remote version has winadj bug");
+    }
 }
 
 /*
@@ -3526,8 +3541,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            s->cur_prompt = new_prompts(ssh->frontend);
            s->cur_prompt->to_server = TRUE;
            s->cur_prompt->name = dupstr("SSH login name");
-           /* 512 is an arbitrary upper limit on username size */
-           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, 512);
+           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
            ret = get_userpass_input(s->cur_prompt, NULL, 0);
            while (ret < 0) {
                ssh->send_ok = 1;
@@ -3573,7 +3587,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
      * Load the public half of any configured keyfile for later use.
      */
     s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
-    if (!filename_is_null(*s->keyfile)) {
+    if (!filename_is_null(s->keyfile)) {
        int keytype;
        logeventf(ssh, "Reading private key file \"%.150s\"",
                  filename_to_str(s->keyfile));
@@ -3820,8 +3834,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                    s->cur_prompt->name = dupstr("SSH key passphrase");
                    add_prompt(s->cur_prompt,
                               dupprintf("Passphrase for key \"%.100s\": ",
-                                        s->publickey_comment),
-                              FALSE, SSH_MAX_PASSWORD_LEN);
+                                        s->publickey_comment), FALSE);
                    ret = get_userpass_input(s->cur_prompt, NULL, 0);
                    while (ret < 0) {
                        ssh->send_ok = 1;
@@ -3846,7 +3859,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                ret = loadrsakey(s->keyfile, &s->key, passphrase,
                                 &error);
                if (passphrase) {
-                   memset(passphrase, 0, strlen(passphrase));
+                   smemclr(passphrase, strlen(passphrase));
                    sfree(passphrase);
                }
                if (ret == 1) {
@@ -3976,7 +3989,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                              (*instr_suf) ? "\n" : "",
                              instr_suf);
                s->cur_prompt->instr_reqd = TRUE;
-               add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+               add_prompt(s->cur_prompt, prompt, FALSE);
                sfree(instr_suf);
            }
        }
@@ -4019,7 +4032,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                              (*instr_suf) ? "\n" : "",
                              instr_suf);
                s->cur_prompt->instr_reqd = TRUE;
-               add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+               add_prompt(s->cur_prompt, prompt, FALSE);
                sfree(instr_suf);
            }
        }
@@ -4032,7 +4045,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            s->cur_prompt->name = dupstr("SSH password");
            add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
                                                ssh->username, ssh->savedhost),
-                      FALSE, SSH_MAX_PASSWORD_LEN);
+                      FALSE);
        }
 
        /*
@@ -4255,6 +4268,35 @@ void sshfwd_write_eof(struct ssh_channel *c)
     ssh_channel_try_eof(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);
+        break;
+      case CHAN_SOCKDATA:
+      case CHAN_SOCKDATA_DORMANT:
+        pfd_close(c->u.pfd.s);
+        break;
+    }
+    c->type = CHAN_ZOMBIE;
+
+    ssh2_channel_check_close(c);
+}
+
 int sshfwd_write(struct ssh_channel *c, char *buf, int len)
 {
     Ssh ssh = c->ssh;
@@ -5746,6 +5788,12 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
            }
        }
+       if (!ssh->hostkey) {
+           bombout(("Couldn't agree a host key algorithm (available: %s)",
+                    str ? str : "(null)"));
+           crStop(0);
+       }
+
        s->guessok = s->guessok &&
            first_in_commasep_string(hostkey_algs[0]->name, str, len);
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server cipher */
@@ -6256,7 +6304,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        assert(ssh->csmac->len <=
               ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
-       memset(keyspace, 0, sizeof(keyspace));
+       smemclr(keyspace, sizeof(keyspace));
     }
 
     logeventf(ssh, "Initialised %.200s client->server encryption",
@@ -6322,7 +6370,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        assert(ssh->scmac->len <=
               ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
        ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
-       memset(keyspace, 0, sizeof(keyspace));
+       smemclr(keyspace, sizeof(keyspace));
     }
     logeventf(ssh, "Initialised %.200s server->client encryption",
              ssh->sccipher->text_name);
@@ -6556,9 +6604,10 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
     /*
      * 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.
+     * sending any more data anyway. Ditto if _we've_ already sent
+     * CLOSE.
      */
-    if (c->closes & CLOSES_RCVD_EOF)
+    if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
        return;
 
     /*
@@ -6568,7 +6617,6 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
      */
     if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
        newwin = OUR_V2_MAXPKT;
-       
 
     /*
      * Only send a WINDOW_ADJUST if there's significantly more window
@@ -6597,7 +6645,8 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin)
         * unexpected CHANNEL_FAILUREs.
         */
        if (newwin == c->v.v2.locmaxwin &&
-           ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
+           ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] &&
+            !(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");
@@ -6978,6 +7027,36 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
     ssh2_channel_got_eof(c);
 
     /*
+     * And we also send an outgoing EOF, if we haven't already, on the
+     * assumption that CLOSE is a pretty forceful announcement that
+     * the remote side is doing away with the entire channel. (If it
+     * had wanted to send us EOF and continue receiving data from us,
+     * it would have just sent CHANNEL_EOF.)
+     */
+    if (!(c->closes & CLOSES_SENT_EOF)) {
+        /*
+         * Make sure we don't read any more from whatever our local
+         * data source is for this channel.
+         */
+        switch (c->type) {
+          case CHAN_MAINSESSION:
+            ssh->send_ok = 0;     /* stop trying to read from stdin */
+            break;
+          case CHAN_X11:
+           x11_override_throttle(c->u.x11.s, 1);
+           break;
+         case CHAN_SOCKDATA:
+           pfd_override_throttle(c->u.pfd.s, 1);
+           break;
+        }
+
+        /*
+         * Send outgoing EOF.
+         */
+        sshfwd_write_eof(c);
+    }
+
+    /*
      * Now process the actual close.
      */
     if (!(c->closes & CLOSES_RCVD_CLOSE)) {
@@ -7433,6 +7512,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int siglen, retlen, len;
        char *q, *agentreq, *ret;
        int try_send;
+       int requested_x11;
+       int requested_agent;
+       int requested_tty;
        int num_env, env_left, env_ok;
        struct Packet *pktout;
        Filename *keyfile;
@@ -7499,7 +7581,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
         * for later use.
         */
        s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
-       if (!filename_is_null(*s->keyfile)) {
+       if (!filename_is_null(s->keyfile)) {
            int keytype;
            logeventf(ssh, "Reading private key file \"%.150s\"",
                      filename_to_str(s->keyfile));
@@ -7645,8 +7727,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            s->cur_prompt = new_prompts(ssh->frontend);
            s->cur_prompt->to_server = TRUE;
            s->cur_prompt->name = dupstr("SSH login name");
-           /* 512 is an arbitrary limit :-( */
-           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, 512); 
+           add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); 
            ret = get_userpass_input(s->cur_prompt, NULL, 0);
            while (ret < 0) {
                ssh->send_ok = 1;
@@ -8052,7 +8133,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                        add_prompt(s->cur_prompt,
                                   dupprintf("Passphrase for key \"%.100s\": ",
                                             s->publickey_comment),
-                                  FALSE, SSH_MAX_PASSWORD_LEN);
+                                  FALSE);
                        ret = get_userpass_input(s->cur_prompt, NULL, 0);
                        while (ret < 0) {
                            ssh->send_ok = 1;
@@ -8084,7 +8165,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    key = ssh2_load_userkey(s->keyfile, passphrase, &error);
                    if (passphrase) {
                        /* burn the evidence */
-                       memset(passphrase, 0, strlen(passphrase));
+                       smemclr(passphrase, strlen(passphrase));
                        sfree(passphrase);
                    }
                    if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
@@ -8432,7 +8513,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                        }
                        add_prompt(s->cur_prompt,
                                   dupprintf("%.*s", prompt_len, prompt),
-                                  echo, SSH_MAX_PASSWORD_LEN);
+                                   echo);
                    }
 
                    if (name_len) {
@@ -8500,6 +8581,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    }
                    ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
 
+                    /*
+                     * Free the prompts structure from this iteration.
+                     * If there's another, a new one will be allocated
+                     * when we return to the top of this while loop.
+                     */
+                    free_prompts(s->cur_prompt);
+
                    /*
                     * Get the next packet in case it's another
                     * INFO_REQUEST.
@@ -8529,7 +8617,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
                                                    ssh->username,
                                                    ssh->savedhost),
-                          FALSE, SSH_MAX_PASSWORD_LEN);
+                          FALSE);
 
                ret = get_userpass_input(s->cur_prompt, NULL, 0);
                while (ret < 0) {
@@ -8631,11 +8719,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     */
                    add_prompt(s->cur_prompt,
                               dupstr("Current password (blank for previously entered password): "),
-                              FALSE, SSH_MAX_PASSWORD_LEN);
+                              FALSE);
                    add_prompt(s->cur_prompt, dupstr("Enter new password: "),
-                              FALSE, SSH_MAX_PASSWORD_LEN);
+                              FALSE);
                    add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
-                              FALSE, SSH_MAX_PASSWORD_LEN);
+                              FALSE);
 
                    /*
                     * Loop until the user manages to enter the same
@@ -8656,7 +8744,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                             */
                            /* burn the evidence */
                            free_prompts(s->cur_prompt);
-                           memset(s->password, 0, strlen(s->password));
+                           smemclr(s->password, strlen(s->password));
                            sfree(s->password);
                            ssh_disconnect(ssh, NULL, "Unable to authenticate",
                                           SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
@@ -8672,7 +8760,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                         * re-enter it if they louse up the new password.)
                         */
                        if (s->cur_prompt->prompts[0]->result[0]) {
-                           memset(s->password, 0, strlen(s->password));
+                           smemclr(s->password, strlen(s->password));
                                /* burn the evidence */
                            sfree(s->password);
                            s->password =
@@ -8739,7 +8827,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                 * We don't need the old password any more, in any
                 * case. Burn the evidence.
                 */
-               memset(s->password, 0, strlen(s->password));
+               smemclr(s->password, strlen(s->password));
                sfree(s->password);
 
            } else {
@@ -8914,6 +9002,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     }
 
     /*
+     * Enable port forwardings.
+     */
+    ssh_setup_portfwd(ssh, ssh->conf);
+
+    /*
+     * Send the CHANNEL_REQUESTS for the main channel.  We send them all
+     * and then start looking for responses, so it's important that the
+     * sending and receiving code below it is kept in sync.
+     */
+
+    /*
      * Potentially enable X11 forwarding.
      */
     if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
@@ -8938,26 +9037,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        end_log_omission(ssh, s->pktout);
        ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum);
        ssh2_pkt_send(ssh, s->pktout);
-
-       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));
-               crStopV;
-           }
-           logevent("X11 forwarding refused");
-       } else {
-           logevent("X11 forwarding enabled");
-           ssh->X11_fwd_enabled = TRUE;
-       }
-    }
-
-    /*
-     * Enable port forwardings.
-     */
-    ssh_setup_portfwd(ssh, ssh->conf);
+       s->requested_x11 = TRUE;
+    } else
+       s->requested_x11 = FALSE;
 
     /*
      * Potentially enable agent forwarding.
@@ -8969,21 +9051,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com");
        ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
        ssh2_pkt_send(ssh, s->pktout);
-
-       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;
-       }
-    }
+       s->requested_agent = TRUE;
+    } else
+       s->requested_agent = FALSE;
 
     /*
      * Now allocate a pty for the session.
@@ -9012,25 +9082,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
        ssh2_pkt_send(ssh, s->pktout);
        ssh->state = SSH_STATE_INTERMED;
-
-       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;
-           }
-           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;
-    }
+       s->requested_tty = TRUE;
+    } else
+       s->requested_tty = FALSE;
 
     /*
      * Send environment variables.
@@ -9038,11 +9092,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      * Simplest thing here is to send all the requests at once, and
      * then wait for a whole bunch of successes or failures.
      */
+    s->num_env = 0;
     if (ssh->mainchan && !ssh->ncmode) {
        char *key, *val;
 
-       s->num_env = 0;
-
        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)) {
@@ -9056,39 +9109,96 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
            s->num_env++;
        }
-
-       if (s->num_env) {
+       if (s->num_env)
            logeventf(ssh, "Sent %d environment variables", s->num_env);
+    }
 
-           s->env_ok = 0;
-           s->env_left = s->num_env;
+    /*
+     * All CHANNEL_REQUESTs sent.  Now collect up the replies.  These
+     * must be in precisely the same order as the requests.
+     */
 
-           while (s->env_left > 0) {
-               crWaitUntilV(pktin);
+    if (s->requested_x11) {
+       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 {
-                   s->env_ok++;
-               }
+       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));
+               crStopV;
+           }
+           logevent("X11 forwarding refused");
+       } else {
+           logevent("X11 forwarding enabled");
+           ssh->X11_fwd_enabled = TRUE;
+       }
+    }
+
+    if (s->requested_agent) {
+       crWaitUntilV(pktin);
 
-               s->env_left--;
+       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 (s->requested_tty) {
+       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;
            }
+           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;
+    }
+
+    if (s->num_env) {
+       s->env_ok = 0;
+       s->env_left = s->num_env;
 
-           if (s->env_ok == s->num_env) {
-               logevent("All environment variables successfully set");
-           } else if (s->env_ok == 0) {
-               logevent("All environment variables refused");
-               c_write_str(ssh, "Server refused to set environment variables\r\n");
+       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 {
-               logeventf(ssh, "%d environment variables refused",
-                         s->num_env - s->env_ok);
-               c_write_str(ssh, "Server refused to set all environment variables\r\n");
+               s->env_ok++;
            }
+
+           s->env_left--;
+       }
+
+       if (s->env_ok == s->num_env) {
+           logevent("All environment variables successfully set");
+       } else if (s->env_ok == 0) {
+           logevent("All environment variables refused");
+           c_write_str(ssh, "Server refused to set environment variables\r\n");
+       } else {
+           logeventf(ssh, "%d environment variables refused",
+                     s->num_env - s->env_ok);
+           c_write_str(ssh, "Server refused to set all environment variables\r\n");
        }
     }
 
@@ -9969,6 +10079,12 @@ static void ssh_unthrottle(void *handle, int bufsize)
            }
        }
     }
+
+    /*
+     * Now process any SSH connection data that was stashed in our
+     * queue while we were frozen.
+     */
+    ssh_process_queued_incoming_data(ssh);
 }
 
 void ssh_send_port_open(void *channel, char *hostname, int port, char *org)