Fixes (mostly from Colin Watson, a couple redone by me) to make Unix
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 0616eff..c3c3f11 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -544,7 +544,7 @@ static int ssh_comp_none_disable(void *handle)
     return 0;
 }
 const static struct ssh_compress ssh_comp_none = {
-    "none",
+    "none", NULL,
     ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
     ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
     ssh_comp_none_disable, NULL
@@ -590,6 +590,17 @@ struct ssh_channel {
      * A channel is completely finished with when all four bits are set.
      */
     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.
+     */
+    int pending_close;
+
     /*
      * True if this channel is causing the underlying connection to be
      * throttled.
@@ -930,6 +941,13 @@ struct ssh_tag {
      * Fully qualified host name, which we need if doing GSSAPI.
      */
     char *fullhostname;
+
+#ifndef NO_GSSAPI
+    /*
+     * GSSAPI libraries for this session.
+     */
+    struct ssh_gss_liblist *gsslibs;
+#endif
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -2844,6 +2862,7 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
                x11_close(c->u.x11.s);
                break;
              case CHAN_SOCKDATA:
+             case CHAN_SOCKDATA_DORMANT:
                pfd_close(c->u.pfd.s);
                break;
            }
@@ -4153,7 +4172,7 @@ void sshfwd_close(struct ssh_channel *c)
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (c && !c->closes) {
+    if (!c->closes) {
        /*
         * If halfopen is true, we have sent
         * CHANNEL_OPEN for this channel, but it hasn't even been
@@ -4165,14 +4184,42 @@ void sshfwd_close(struct ssh_channel *c)
            if (ssh->version == 1) {
                send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                            PKT_END);
+               c->closes = 1;                 /* sent MSG_CLOSE */
            } else {
-               struct Packet *pktout;
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-               ssh2_pkt_adduint32(pktout, c->remoteid);
-               ssh2_pkt_send(ssh, pktout);
+               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");
+               }
            }
        }
-       c->closes = 1;                 /* sent MSG_CLOSE */
+
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
@@ -4311,6 +4358,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
 
        rpf = del234(ssh->rportfwds, pf);
        assert(rpf == pf);
+       pf->pfrec->remote = NULL;
        free_rportfwd(pf);
     }
 }
@@ -4487,6 +4535,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
            logeventf(ssh, "Cancelling %s", message);
            sfree(message);
 
+           /* epf->remote or epf->local may be NULL if setting up a
+            * forwarding failed. */
            if (epf->remote) {
                struct ssh_rportfwd *rpf = epf->remote;
                struct Packet *pktout;
@@ -4696,6 +4746,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->throttling_conn = 0;
            c->type = CHAN_X11; /* identify channel type */
            add234(ssh->channels, c);
@@ -4725,6 +4776,7 @@ 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->throttling_conn = 0;
        c->type = CHAN_AGENT;   /* identify channel type */
        c->u.a.lensofar = 0;
@@ -4779,6 +4831,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->throttling_conn = 0;
            c->type = CHAN_SOCKDATA;    /* identify channel type */
            add234(ssh->channels, c);
@@ -5369,6 +5422,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        int n_preferred_ciphers;
        const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
        const struct ssh_compress *preferred_comp;
+       int userauth_succeeded;     /* for delayed compression */
+       int pending_compression;
        int got_session_id, activated_authconn;
        struct Packet *pktout;
         int dlgret;
@@ -5384,6 +5439,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     s->cscomp_tobe = s->sccomp_tobe = NULL;
 
     s->got_session_id = s->activated_authconn = FALSE;
+    s->userauth_succeeded = FALSE;
+    s->pending_compression = FALSE;
 
     /*
      * Be prepared to work around the buggy MAC problem.
@@ -5548,26 +5605,32 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (i < s->nmacs - 1)
                ssh2_pkt_addstring_str(s->pktout, ",");
        }
-       /* List client->server compression algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       assert(lenof(compressions) > 1);
-       ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
-       for (i = 0; i < lenof(compressions); i++) {
-           const struct ssh_compress *c = compressions[i];
-           if (c != s->preferred_comp) {
+       /* List client->server compression algorithms,
+        * then server->client compression algorithms. (We use the
+        * same set twice.) */
+       for (j = 0; j < 2; j++) {
+           ssh2_pkt_addstring_start(s->pktout);
+           assert(lenof(compressions) > 1);
+           /* Prefer non-delayed versions */
+           ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+           /* We don't even list delayed versions of algorithms until
+            * they're allowed to be used, to avoid a race. See the end of
+            * this function. */
+           if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
                ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout, c->name);
+               ssh2_pkt_addstring_str(s->pktout,
+                                      s->preferred_comp->delayed_name);
            }
-       }
-       /* List server->client compression algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       assert(lenof(compressions) > 1);
-       ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
-       for (i = 0; i < lenof(compressions); i++) {
-           const struct ssh_compress *c = compressions[i];
-           if (c != s->preferred_comp) {
-               ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout, c->name);
+           for (i = 0; i < lenof(compressions); i++) {
+               const struct ssh_compress *c = compressions[i];
+               if (c != s->preferred_comp) {
+                   ssh2_pkt_addstring_str(s->pktout, ",");
+                   ssh2_pkt_addstring_str(s->pktout, c->name);
+                   if (s->userauth_succeeded && c->delayed_name) {
+                       ssh2_pkt_addstring_str(s->pktout, ",");
+                       ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
+                   }
+               }
            }
        }
        /* List client->server languages. Empty list. */
@@ -5716,6 +5779,13 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (in_commasep_string(c->name, str, len)) {
                s->cscomp_tobe = c;
                break;
+           } else if (in_commasep_string(c->delayed_name, str, len)) {
+               if (s->userauth_succeeded) {
+                   s->cscomp_tobe = c;
+                   break;
+               } else {
+                   s->pending_compression = TRUE;  /* try this later */
+               }
            }
        }
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client compression */
@@ -5725,8 +5795,19 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            if (in_commasep_string(c->name, str, len)) {
                s->sccomp_tobe = c;
                break;
+           } else if (in_commasep_string(c->delayed_name, str, len)) {
+               if (s->userauth_succeeded) {
+                   s->sccomp_tobe = c;
+                   break;
+               } else {
+                   s->pending_compression = TRUE;  /* try this later */
+               }
            }
        }
+       if (s->pending_compression) {
+           logevent("Server supports delayed compression; "
+                    "will try this later");
+       }
        ssh_pkt_getstring(pktin, &str, &len);  /* client->server language */
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client language */
        s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
@@ -6262,19 +6343,52 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * start.
      * 
      * We _also_ go back to the start if we see pktin==NULL and
-     * inlen==-1, because this is a special signal meaning
+     * inlen negative, because this is a special signal meaning
      * `initiate client-driven rekey', and `in' contains a message
      * giving the reason for the rekey.
+     *
+     * inlen==-1 means always initiate a rekey;
+     * inlen==-2 means that userauth has completed successfully and
+     *   we should consider rekeying (for delayed compression).
      */
     while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
-            (!pktin && inlen == -1))) {
+            (!pktin && inlen < 0))) {
         wait_for_rekey:
        crReturn(1);
     }
     if (pktin) {
        logevent("Server initiated key re-exchange");
     } else {
+       if (inlen == -2) {
+           /* 
+            * authconn has seen a USERAUTH_SUCCEEDED. Time to enable
+            * delayed compression, if it's available.
+            *
+            * draft-miller-secsh-compression-delayed-00 says that you
+            * negotiate delayed compression in the first key exchange, and
+            * both sides start compressing when the server has sent
+            * USERAUTH_SUCCESS. This has a race condition -- the server
+            * can't know when the client has seen it, and thus which incoming
+            * packets it should treat as compressed.
+            *
+            * Instead, we do the initial key exchange without offering the
+            * delayed methods, but note if the server offers them; when we
+            * get here, if a delayed method was available that was higher
+            * on our list than what we got, we initiate a rekey in which we
+            * _do_ list the delayed methods (and hopefully get it as a
+            * result). Subsequent rekeys will do the same.
+            */
+           assert(!s->userauth_succeeded); /* should only happen once */
+           s->userauth_succeeded = TRUE;
+           if (!s->pending_compression)
+               /* Can't see any point rekeying. */
+               goto wait_for_rekey;       /* this is utterly horrid */
+           /* else fall through to rekey... */
+           s->pending_compression = FALSE;
+       }
         /*
+        * Now we've decided to rekey.
+        *
          * Special case: if the server bug is set that doesn't
          * allow rekeying, we give a different log message and
          * continue waiting. (If such a server _initiates_ a rekey,
@@ -6292,7 +6406,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                     schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC,
                                    ssh2_timer, ssh);
             }
-            goto wait_for_rekey;       /* this is utterly horrid */
+            goto wait_for_rekey;       /* this is still utterly horrid */
         } else {
             logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in);
         }
@@ -6345,7 +6459,7 @@ static int ssh2_try_send(struct ssh_channel *c)
     return bufchain_size(&c->v.v2.outbuffer);
 }
 
-static void ssh2_try_send_and_unthrottle(struct ssh_channel *c)
+static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
 {
     int bufsize;
     if (c->closes)
@@ -6369,6 +6483,19 @@ static void ssh2_try_send_and_unthrottle(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;
+    }
 }
 
 /*
@@ -6379,6 +6506,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->throttling_conn = FALSE;
     c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
        ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
@@ -6562,7 +6690,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
        return;
     if (!c->closes) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
-       ssh2_try_send_and_unthrottle(c);
+       ssh2_try_send_and_unthrottle(ssh, c);
     }
 }
 
@@ -7128,12 +7256,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 }
 
 /*
- * Buffer banner messages for later display at some convenient point.
+ * Buffer banner messages for later display at some convenient point,
+ * if we're going to display them.
  */
 static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
 {
     /* Arbitrary limit to prevent unbounded inflation of buffer */
-    if (bufchain_size(&ssh->banner) <= 131072) {
+    if (ssh->cfg.ssh_show_banner &&
+       bufchain_size(&ssh->banner) <= 131072) {
        char *banner = NULL;
        int size = 0;
        ssh_pkt_getstring(pktin, &banner, &size);
@@ -7187,7 +7317,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int tried_gssapi;
 #endif
        int kbd_inter_refused;
-       int we_are_in;
+       int we_are_in, userauth_success;
        prompts_t *cur_prompt;
        int num_prompts;
        char username[100];
@@ -7223,7 +7353,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     crBegin(ssh->do_ssh2_authconn_crstate);
 
     s->done_service_req = FALSE;
-    s->we_are_in = FALSE;
+    s->we_are_in = s->userauth_success = FALSE;
 #ifndef NO_GSSAPI
     s->tried_gssapi = FALSE;
 #endif
@@ -7479,6 +7609,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        }
 
        while (1) {
+           char *methods = NULL;
+           int methlen = 0;
+
            /*
             * Wait for the result of the last authentication request.
             */
@@ -7510,7 +7643,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
            }
            if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                logevent("Access granted");
-               s->we_are_in = TRUE;
+               s->we_are_in = s->userauth_success = TRUE;
                break;
            }
 
@@ -7528,8 +7661,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * helpfully try next.
             */
            if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
-               char *methods;
-               int methlen;
                ssh_pkt_getstring(pktin, &methods, &methlen);
                if (!ssh2_pkt_getbool(pktin)) {
                    /*
@@ -7585,11 +7716,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    in_commasep_string("password", methods, methlen);
                s->can_keyb_inter = ssh->cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
-#ifndef NO_GSSAPI              
-               ssh_gss_init();
+#ifndef NO_GSSAPI
+               if (!ssh->gsslibs)
+                   ssh->gsslibs = ssh_gss_setup(&ssh->cfg);
                s->can_gssapi = ssh->cfg.try_gssapi_auth &&
                    in_commasep_string("gssapi-with-mic", methods, methlen) &&
-                   n_ssh_gss_libraries > 0;
+                   ssh->gsslibs->nlibraries > 0;
 #endif
            }
 
@@ -7941,9 +8073,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    s->gsslib = NULL;
                    for (i = 0; i < ngsslibs; i++) {
                        int want_id = ssh->cfg.ssh_gsslist[i];
-                       for (j = 0; j < n_ssh_gss_libraries; j++)
-                           if (ssh_gss_libraries[j].id == want_id) {
-                               s->gsslib = &ssh_gss_libraries[j];
+                       for (j = 0; j < ssh->gsslibs->nlibraries; j++)
+                           if (ssh->gsslibs->libraries[j].id == want_id) {
+                               s->gsslib = &ssh->gsslibs->libraries[j];
                                goto got_gsslib;   /* double break */
                            }
                    }
@@ -8493,11 +8625,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                sfree(s->password);
 
            } else {
+               char *str = dupprintf("No supported authentication methods available"
+                                     " (server sent: %.*s)",
+                                     methlen, methods);
 
-               ssh_disconnect(ssh, NULL,
+               ssh_disconnect(ssh, str,
                               "No supported authentication methods available",
                               SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
                               FALSE);
+               sfree(str);
+
                crStopV;
 
            }
@@ -8514,6 +8651,20 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     if (s->agent_response)
        sfree(s->agent_response);
 
+    if (s->userauth_success) {
+       /*
+        * We've just received USERAUTH_SUCCESS, and we haven't sent any
+        * packets since. Signal the transport layer to consider enacting
+        * delayed compression.
+        *
+        * (Relying on we_are_in is not sufficient, as
+        * draft-miller-secsh-compression-delayed is quite clear that it
+        * triggers on USERAUTH_SUCCESS specifically, and we_are_in can
+        * become set for other reasons.)
+        */
+       do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL);
+    }
+
     /*
      * Now the connection protocol has started, one way or another.
      */
@@ -8936,7 +9087,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * Try to send data on all channels if we can.
             */
            for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
-               ssh2_try_send_and_unthrottle(c);
+               ssh2_try_send_and_unthrottle(ssh, c);
        }
     }
 
@@ -8980,10 +9131,9 @@ static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
     /* log the debug message */
     char *msg;
     int msglen;
-    int always_display;
 
-    /* XXX maybe we should actually take notice of this */
-    always_display = ssh2_pkt_getbool(pktin);
+    /* XXX maybe we should actually take notice of the return value */
+    ssh2_pkt_getbool(pktin);
     ssh_pkt_getstring(pktin, &msg, &msglen);
 
     logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
@@ -9218,6 +9368,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data);
     ssh->kex_in_progress = FALSE;
 
+#ifndef NO_GSSAPI
+    ssh->gsslibs = NULL;
+#endif
+
     p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
     if (p != NULL)
        return p;
@@ -9278,6 +9432,7 @@ static void ssh_free(void *handle)
                    x11_close(c->u.x11.s);
                break;
              case CHAN_SOCKDATA:
+             case CHAN_SOCKDATA_DORMANT:
                if (c->u.pfd.s != NULL)
                    pfd_close(c->u.pfd.s);
                break;
@@ -9290,7 +9445,7 @@ static void ssh_free(void *handle)
 
     if (ssh->rportfwds) {
        while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
-           sfree(pf);
+           free_rportfwd(pf);
        freetree234(ssh->rportfwds);
        ssh->rportfwds = NULL;
     }
@@ -9314,6 +9469,10 @@ static void ssh_free(void *handle)
     if (ssh->pinger)
        pinger_free(ssh->pinger);
     bufchain_clear(&ssh->queued_incoming_data);
+#ifndef NO_GSSAPI
+    if (ssh->gsslibs)
+       ssh_gss_cleanup(ssh->gsslibs);
+#endif
     sfree(ssh);
 
     random_unref();