Implement zlib@openssh.com, using the rekey-after-userauth method suggested in
authorjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Fri, 4 Mar 2011 22:34:47 +0000 (22:34 +0000)
committerjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Fri, 4 Mar 2011 22:34:47 +0000 (22:34 +0000)
the wishlist entry.

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

ssh.c
ssh.h
sshzlib.c

diff --git a/ssh.c b/ssh.c
index a7f1c6b..95fa2f2 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
@@ -5422,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;
@@ -5437,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.
@@ -5601,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. */
@@ -5769,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 */
@@ -5778,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;
@@ -6315,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,
@@ -6345,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);
         }
@@ -7256,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];
@@ -7292,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
@@ -7582,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;
            }
 
@@ -8590,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.
      */
diff --git a/ssh.h b/ssh.h
index 6acd878..90dafed 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -251,6 +251,9 @@ struct ssh_signkey {
 
 struct ssh_compress {
     char *name;
+    /* For zlib@openssh.com: if non-NULL, this name will be considered once
+     * userauth has completed successfully. */
+    char *delayed_name;
     void *(*compress_init) (void);
     void (*compress_cleanup) (void *);
     int (*compress) (void *, unsigned char *block, int len,
index cc28724..25ccd25 100644 (file)
--- a/sshzlib.c
+++ b/sshzlib.c
@@ -1371,6 +1371,7 @@ int main(int argc, char **argv)
 
 const struct ssh_compress ssh_zlib = {
     "zlib",
+    "zlib@openssh.com", /* delayed version */
     zlib_compress_init,
     zlib_compress_cleanup,
     zlib_compress_block,