Add single-DES support in SSH2
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index e913571..3a02b12 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -159,6 +159,8 @@ static const char *const ssh2_disconnect_reasons[] = {
  */
 #define BUG_CHOKES_ON_SSH1_IGNORE                 1
 #define BUG_SSH2_HMAC                             2
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
+
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -234,15 +236,13 @@ extern void pfd_override_throttle(Socket s, int enable);
 #define OUR_V2_WINSIZE 16384
 
 /*
- * Ciphers for SSH2. We miss out single-DES because it isn't
- * supported; also 3DES and Blowfish are both done differently from
- * SSH1. (3DES uses outer chaining; Blowfish has the opposite
- * endianness and different-sized keys.)
+ * Ciphers for SSH2.
  */
 const static struct ssh2_ciphers *ciphers[] = {
     &ssh2_aes,
     &ssh2_blowfish,
     &ssh2_3des,
+    &ssh2_des,
 };
 
 const static struct ssh_kex *kex_algs[] = {
@@ -940,23 +940,18 @@ static int s_wrpkt_prepare(void)
 
     pktout.body[-1] = pktout.type;
 
-#ifdef DUMP_PACKETS
-    debug(("Packet payload pre-compression:\n"));
-    dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
-
     if (ssh1_compressing) {
        unsigned char *compblk;
        int complen;
+#ifdef DUMP_PACKETS
+       debug(("Packet payload pre-compression:\n"));
+       dmemdump(pktout.body - 1, pktout.length + 1);
+#endif
        zlib_compress_block(pktout.body - 1, pktout.length + 1,
                            &compblk, &complen);
        ssh1_pktout_size(complen - 1);
        memcpy(pktout.body - 1, compblk, complen);
        sfree(compblk);
-#ifdef DUMP_PACKETS
-       debug(("Packet payload post-compression:\n"));
-       dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
     }
 
     len = pktout.length + 5;          /* type and CRC */
@@ -1239,13 +1234,15 @@ static int ssh2_pkt_construct(void)
     /*
      * Compress packet payload.
      */
-#ifdef DUMP_PACKETS
-    debug(("Pre-compression payload:\n"));
-    dmemdump(pktout.data + 5, pktout.length - 5);
-#endif
     {
        unsigned char *newpayload;
        int newlen;
+#ifdef DUMP_PACKETS
+       if (cscomp && cscomp != &ssh_comp_none) {
+           debug(("Pre-compression payload:\n"));
+           dmemdump(pktout.data + 5, pktout.length - 5);
+       }
+#endif
        if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
                                       &newpayload, &newlen)) {
            pktout.length = 5;
@@ -1418,17 +1415,15 @@ static void ssh_detect_bugs(char *vstring)
     char *imp;                        /* pointer to implementation part */
     imp = vstring;
     imp += strcspn(imp, "-");
-    if (*imp)
-       imp++;
+    if (*imp) imp++;
     imp += strcspn(imp, "-");
-    if (*imp)
-       imp++;
+    if (*imp) imp++;
 
     ssh_remote_bugs = 0;
 
     if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
        !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
-       !strcmp(imp, "1.2.22")) {
+       !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) {
        /*
         * These versions don't support SSH1_MSG_IGNORE, so we have
         * to use a different defence against password length
@@ -1438,6 +1433,16 @@ static void ssh_detect_bugs(char *vstring)
        logevent("We believe remote version has SSH1 ignore bug");
     }
 
+    if (!strcmp(imp, "Cisco-1.25")) {
+       /*
+        * These versions need a plain password sent; they can't
+        * handle having a null and a random length of data after
+        * the password.
+        */
+       ssh_remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+       logevent("We believe remote version needs a plain SSH1 password");
+    }
+
     if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
        !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
        !strncmp(imp, "2.1 ", 4)) {
@@ -1479,8 +1484,8 @@ static int do_ssh_init(unsigned char c)
        crReturn(1);                   /* get another character */
     }
 
-    vstring = smalloc(16);
     vstrsize = 16;
+    vstring = smalloc(vstrsize);
     strcpy(vstring, "SSH-");
     vslen = 4;
     i = 0;
@@ -1506,10 +1511,10 @@ static int do_ssh_init(unsigned char c)
 
     vstring[vslen] = 0;
     vlog = smalloc(20 + vslen);
+    vstring[strcspn (vstring, "\r\n")] = '\0'; /* remove end-of-line chars */
     sprintf(vlog, "Server version: %s", vstring);
-    ssh_detect_bugs(vstring);
-    vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
+    ssh_detect_bugs(vstring);
     sfree(vlog);
 
     /*
@@ -2405,26 +2410,17 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                 * use of the fact that the password is interpreted
                 * as a C string: so we can append a NUL, then some
                 * random data.
+                * 
+                * One server (a Cisco one) can deal with neither
+                * SSH1_MSG_IGNORE _nor_ a padded password string.
+                * For this server we are left with no defences
+                * against password length sniffing.
                 */
-               if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
-                   char string[64];
-                   char *s;
-                   int len;
-
-                   len = strlen(password);
-                   if (len < sizeof(string)) {
-                       s = string;
-                       strcpy(string, password);
-                       len++;         /* cover the zero byte */
-                       while (len < sizeof(string)) {
-                           string[len++] = (char) random_byte();
-                       }
-                   } else {
-                       s = password;
-                   }
-                   send_packet(pwpkt_type, PKT_INT, len,
-                               PKT_DATA, s, len, PKT_END);
-               } else {
+               if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+                   /*
+                    * The server can deal with SSH1_MSG_IGNORE, so
+                    * we can use the primary defence.
+                    */
                    int bottom, top, pwlen, i;
                    char *randomstr;
 
@@ -2456,7 +2452,45 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                                         PKT_STR, randomstr, PKT_END);
                        }
                    }
+                   logevent("Sending password with camouflage packets");
                    ssh_pkt_defersend();
+               } 
+               else if (!(ssh_remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+                   /*
+                    * The server can't deal with SSH1_MSG_IGNORE
+                    * but can deal with padded passwords, so we
+                    * can use the secondary defence.
+                    */
+                   char string[64];
+                   char *s;
+                   int len;
+
+                   len = strlen(password);
+                   if (len < sizeof(string)) {
+                       s = string;
+                       strcpy(string, password);
+                       len++;         /* cover the zero byte */
+                       while (len < sizeof(string)) {
+                           string[len++] = (char) random_byte();
+                       }
+                   } else {
+                       s = password;
+                   }
+                   logevent("Sending length-padded password");
+                   send_packet(pwpkt_type, PKT_INT, len,
+                               PKT_DATA, s, len, PKT_END);
+               } else {
+                   /*
+                    * The server has _both_
+                    * BUG_CHOKES_ON_SSH1_IGNORE and
+                    * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+                    * therefore nothing we can do.
+                    */
+                   int len;
+                   len = strlen(password);
+                   logevent("Sending unpadded password");
+                   send_packet(pwpkt_type, PKT_INT, len,
+                               PKT_DATA, password, len, PKT_END);
                }
            } else {
                send_packet(pwpkt_type, PKT_STR, password, PKT_END);
@@ -2487,19 +2521,29 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 void sshfwd_close(struct ssh_channel *c)
 {
     if (c && !c->closes) {
-       if (ssh_version == 1) {
-           send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
-                       PKT_END);
-       } else {
-           ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-           ssh2_pkt_adduint32(c->remoteid);
-           ssh2_pkt_send();
+       /*
+        * If the channel's remoteid is -1, we have sent
+        * CHANNEL_OPEN for this channel, but it hasn't even been
+        * acknowledged by the server. So we must set a close flag
+        * on it now, and then when the server acks the channel
+        * open, we can close it then.
+        */
+       if (c->remoteid != -1) {
+           if (ssh_version == 1) {
+               send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+                           PKT_END);
+           } else {
+               ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+               ssh2_pkt_adduint32(c->remoteid);
+               ssh2_pkt_send();
+           }
        }
        c->closes = 1;
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
-       } else if (c->type == CHAN_SOCKDATA) {
+       } else if (c->type == CHAN_SOCKDATA ||
+                  c->type == CHAN_SOCKDATA_DORMANT) {
            c->u.pfd.s = NULL;
            logevent("Forwarded port closed");
        }
@@ -2866,9 +2910,21 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                if (c && c->type == CHAN_SOCKDATA_DORMANT) {
                    c->remoteid = localid;
                    c->type = CHAN_SOCKDATA;
+                   c->v.v1.throttling = 0;
                    pfd_confirm(c->u.pfd.s);
                }
 
+               if (c && c->closes) {
+                   /*
+                    * We have a pending close on this channel,
+                    * which we decided on before the server acked
+                    * the channel open. So now we know the
+                    * remoteid, we can close it again.
+                    */
+                   send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+                               PKT_END);
+               }
+
            } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
                unsigned int remoteid = GET_32BIT(pktin.body);
                unsigned int localid = GET_32BIT(pktin.body+4);
@@ -3114,7 +3170,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            n_preferred_ciphers++;
            break;
          case CIPHER_DES:
-           /* Not supported in SSH2; silently drop */
+           preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+           n_preferred_ciphers++;
            break;
          case CIPHER_3DES:
            preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
@@ -3295,6 +3352,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            break;
        }
     }
+    if (!cscipher_tobe) {
+       bombout(("Couldn't agree a client-to-server cipher (available: %s)", str));
+       crReturn(0);
+    }
+
     ssh2_pkt_getstring(&str, &len);    /* server->client cipher */
     warn = 0;
     for (i = 0; i < n_preferred_ciphers; i++) {
@@ -3315,6 +3377,11 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            break;
        }
     }
+    if (!sccipher_tobe) {
+       bombout(("Couldn't agree a server-to-client cipher (available: %s)", str));
+       crReturn(0);
+    }
+
     ssh2_pkt_getstring(&str, &len);    /* client->server mac */
     for (i = 0; i < nmacs; i++) {
        if (in_commasep_string(maclist[i]->name, str, len)) {
@@ -3442,15 +3509,6 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
-     * Expect SSH2_MSG_NEWKEYS from server.
-     */
-    crWaitUntil(ispkt);
-    if (pktin.type != SSH2_MSG_NEWKEYS) {
-       bombout(("expected new-keys packet from server"));
-       crReturn(0);
-    }
-
-    /*
      * Authenticate remote host: verify host key. (We've already
      * checked the signature of the exchange hash.)
      */
@@ -3473,6 +3531,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     ssh2_pkt_send();
 
     /*
+     * Expect SSH2_MSG_NEWKEYS from server.
+     */
+    crWaitUntil(ispkt);
+    if (pktin.type != SSH2_MSG_NEWKEYS) {
+       bombout(("expected new-keys packet from server"));
+       crReturn(0);
+    }
+
+    /*
      * Create and initialise session keys.
      */
     cscipher = cscipher_tobe;
@@ -4739,8 +4806,6 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect message");
                crReturnV;
-           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
-               continue;              /* exit status et al; ignore (FIXME?) */
            } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) {
                unsigned i = ssh2_pkt_getuint32();
                struct ssh_channel *c;
@@ -4828,11 +4893,22 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    continue;          /* dunno why they're confirming this */
                c->remoteid = ssh2_pkt_getuint32();
                c->type = CHAN_SOCKDATA;
-               c->closes = 0;
                c->v.v2.remwindow = ssh2_pkt_getuint32();
                c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
                bufchain_init(&c->v.v2.outbuffer);
-               pfd_confirm(c->u.pfd.s);
+               if (c->u.pfd.s)
+                   pfd_confirm(c->u.pfd.s);
+               if (c->closes) {
+                   /*
+                    * We have a pending close on this channel,
+                    * which we decided on before the server acked
+                    * the channel open. So now we know the
+                    * remoteid, we can close it again.
+                    */
+                   ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                   ssh2_pkt_adduint32(c->remoteid);
+                   ssh2_pkt_send();
+               }
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
                unsigned i = ssh2_pkt_getuint32();
                struct ssh_channel *c;
@@ -4848,6 +4924,46 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                del234(ssh_channels, c);
                sfree(c);
+           } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+               unsigned localid;
+               char *type;
+               int typelen, want_reply;
+               struct ssh_channel *c;
+
+               localid = ssh2_pkt_getuint32();
+               ssh2_pkt_getstring(&type, &typelen);
+               want_reply = ssh2_pkt_getbool();
+
+               /*
+                * 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[80];
+                   sprintf(buf, "Received channel request for nonexistent"
+                           " channel %d", localid);
+                   logevent(buf);
+                   ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+                   ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
+                   ssh2_pkt_addstring(buf);
+                   ssh2_pkt_addstring("en");   /* language tag */
+                   ssh2_pkt_send();
+                   connection_fatal(buf);
+                   ssh_state = SSH_STATE_CLOSED;
+                   crReturnV;
+               }
+
+               /*
+                * We don't recognise any form of channel request,
+                * so we now either ignore the request or respond
+                * with CHANNEL_FAILURE, depending on want_reply.
+                */
+               if (want_reply) {
+                   ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+                   ssh2_pkt_adduint32(c->remoteid);
+                   ssh2_pkt_send();
+               }
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
                char *type;
                int typelen;