Improve SSH2 host key abstraction into a generic `signing key'
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 3eddd7b..92afd08 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -51,6 +51,7 @@
 #define SSH1_CMSG_EXIT_CONFIRMATION               33   /* 0x21 */
 #define SSH1_MSG_IGNORE                           32   /* 0x20 */
 #define SSH1_MSG_DEBUG                            36   /* 0x24 */
+#define SSH1_CMSG_REQUEST_COMPRESSION             37   /* 0x25 */
 #define SSH1_CMSG_AUTH_TIS                        39   /* 0x27 */
 #define SSH1_SMSG_AUTH_TIS_CHALLENGE              40   /* 0x28 */
 #define SSH1_CMSG_AUTH_TIS_RESPONSE               41   /* 0x29 */
@@ -170,8 +171,8 @@ const static struct ssh_cipher *ciphers[] = { &ssh_blowfish_ssh2, &ssh_3des_ssh2
 extern const struct ssh_kex ssh_diffiehellman;
 const static struct ssh_kex *kex_algs[] = { &ssh_diffiehellman };
 
-extern const struct ssh_hostkey ssh_dss;
-const static struct ssh_hostkey *hostkey_algs[] = { &ssh_dss };
+extern const struct ssh_signkey ssh_dss;
+const static struct ssh_signkey *hostkey_algs[] = { &ssh_dss };
 
 extern const struct ssh_mac ssh_md5, ssh_sha1, ssh_sha1_buggy;
 
@@ -186,10 +187,19 @@ const static struct ssh_mac *macs[] = {
 const static struct ssh_mac *buggymacs[] = {
     &ssh_sha1_buggy, &ssh_md5, &ssh_mac_none };
 
+static void ssh_comp_none_init(void) { }
+static int ssh_comp_none_block(unsigned char *block, int len,
+                              unsigned char **outblock, int *outlen) {
+    return 0;
+}
 const static struct ssh_compress ssh_comp_none = {
-    "none"
+    "none",
+    ssh_comp_none_init, ssh_comp_none_block,
+    ssh_comp_none_init, ssh_comp_none_block
 };
-const static struct ssh_compress *compressions[] = { &ssh_comp_none };
+extern const struct ssh_compress ssh_zlib;
+const static struct ssh_compress *compressions[] = {
+    &ssh_zlib, &ssh_comp_none };
 
 /*
  * 2-3-4 tree storing channels.
@@ -226,6 +236,8 @@ static SHA_State exhash;
 static Socket s = NULL;
 
 static unsigned char session_key[32];
+static int ssh1_compressing;
+static int ssh_agentfwd_enabled;
 static const struct ssh_cipher *cipher = NULL;
 static const struct ssh_cipher *cscipher = NULL;
 static const struct ssh_cipher *sccipher = NULL;
@@ -234,7 +246,7 @@ static const struct ssh_mac *scmac = NULL;
 static const struct ssh_compress *cscomp = NULL;
 static const struct ssh_compress *sccomp = NULL;
 static const struct ssh_kex *kex = NULL;
-static const struct ssh_hostkey *hostkey = NULL;
+static const struct ssh_signkey *hostkey = NULL;
 int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
 
 static char *savedhost;
@@ -373,9 +385,6 @@ next_packet:
     debug(("\r\n"));
 #endif
 
-    pktin.type = pktin.data[st->pad];
-    pktin.body = pktin.data + st->pad + 1;
-
     st->realcrc = crc32(pktin.data, st->biglen-4);
     st->gotcrc = GET_32BIT(pktin.data+st->biglen-4);
     if (st->gotcrc != st->realcrc) {
@@ -383,6 +392,39 @@ next_packet:
         crReturn(0);
     }
 
+    pktin.body = pktin.data + st->pad + 1;
+
+    if (ssh1_compressing) {
+       unsigned char *decompblk;
+       int decomplen;
+#if 0
+       int i;
+       debug(("Packet payload pre-decompression:\n"));
+       for (i = -1; i < pktin.length; i++)
+           debug(("  %02x", (unsigned char)pktin.body[i]));
+       debug(("\r\n"));
+#endif
+       zlib_decompress_block(pktin.body-1, pktin.length+1,
+                             &decompblk, &decomplen);
+
+       if (pktin.maxlen < st->pad + decomplen) {
+           pktin.maxlen = st->pad + decomplen;
+           pktin.data = realloc(pktin.data, pktin.maxlen+APIEXTRA);
+           if (!pktin.data)
+               fatalbox("Out of memory");
+       }
+
+       memcpy(pktin.body-1, decompblk, decomplen);
+       free(decompblk);
+       pktin.length = decomplen-1;
+#if 0
+       debug(("Packet payload post-decompression:\n"));
+       for (i = -1; i < pktin.length; i++)
+           debug(("  %02x", (unsigned char)pktin.body[i]));
+       debug(("\r\n"));
+#endif
+    }
+
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
         pktin.type == SSH1_SMSG_STDERR_DATA ||
         pktin.type == SSH1_MSG_DEBUG ||
@@ -395,6 +437,8 @@ next_packet:
         }
     }
 
+    pktin.type = pktin.body[-1];
+
     if (pktin.type == SSH1_MSG_DEBUG) {
        /* log debug message */
        char buf[80];
@@ -515,6 +559,34 @@ next_packet:
     }
     st->incoming_sequence++;               /* whether or not we MACed */
 
+    /*
+     * Decompress packet payload.
+     */
+    {
+       unsigned char *newpayload;
+       int newlen;
+       if (sccomp && sccomp->decompress(pktin.data+5, pktin.length-5,
+                                        &newpayload, &newlen)) {
+           if (pktin.maxlen < newlen+5) {
+               pktin.maxlen = newlen+5;
+               pktin.data = (pktin.data == NULL ? malloc(pktin.maxlen+APIEXTRA) :
+                             realloc(pktin.data, pktin.maxlen+APIEXTRA));
+               if (!pktin.data)
+                   fatalbox("Out of memory");
+           }
+           pktin.length = 5 + newlen;
+           memcpy(pktin.data+5, newpayload, newlen);
+#if 0
+           debug(("Post-decompression payload:\r\n"));
+           for (st->i = 0; st->i < newlen; st->i++)
+               debug(("  %02x", (unsigned char)pktin.data[5+st->i]));
+           debug(("\r\n"));
+#endif
+
+           free(newpayload);
+       }
+    }
+
     pktin.savedpos = 6;
     pktin.type = pktin.data[5];
 
@@ -524,7 +596,7 @@ next_packet:
     crFinish(0);
 }
 
-static void s_wrpkt_start(int type, int len) {
+static void ssh1_pktout_size(int len) {
     int pad, biglen;
 
     len += 5;                         /* type and CRC */
@@ -546,20 +618,46 @@ static void s_wrpkt_start(int type, int len) {
        if (!pktout.data)
            fatalbox("Out of memory");
     }
+    pktout.body = pktout.data+4+pad+1;
+}
 
+static void s_wrpkt_start(int type, int len) {
+    ssh1_pktout_size(len);
     pktout.type = type;
-    pktout.body = pktout.data+4+pad+1;
 }
 
 static void s_wrpkt(void) {
     int pad, len, biglen, i;
     unsigned long crc;
 
+    pktout.body[-1] = pktout.type;
+
+    if (ssh1_compressing) {
+       unsigned char *compblk;
+       int complen;
+#if 0
+       debug(("Packet payload pre-compression:\n"));
+       for (i = -1; i < pktout.length; i++)
+           debug(("  %02x", (unsigned char)pktout.body[i]));
+       debug(("\r\n"));
+#endif
+       zlib_compress_block(pktout.body-1, pktout.length+1,
+                           &compblk, &complen);
+       ssh1_pktout_size(complen-1);
+       memcpy(pktout.body-1, compblk, complen);
+       free(compblk);
+#if 0
+       debug(("Packet payload post-compression:\n"));
+       for (i = -1; i < pktout.length; i++)
+           debug(("  %02x", (unsigned char)pktout.body[i]));
+       debug(("\r\n"));
+#endif
+    }
+
     len = pktout.length + 5;          /* type and CRC */
     pad = 8 - (len%8);
     biglen = len + pad;
 
-    pktout.body[-1] = pktout.type;
     for (i=0; i<pad; i++)
        pktout.data[i+4] = random_byte();
     crc = crc32(pktout.data+4, biglen-4);
@@ -768,6 +866,26 @@ static void ssh2_pkt_send(void) {
     static unsigned long outgoing_sequence = 0;
 
     /*
+     * Compress packet payload.
+     */
+#if 0
+    debug(("Pre-compression payload:\r\n"));
+    for (i = 5; i < pktout.length; i++)
+       debug(("  %02x", (unsigned char)pktout.data[i]));
+    debug(("\r\n"));
+#endif
+    {
+       unsigned char *newpayload;
+       int newlen;
+       if (cscomp && cscomp->compress(pktout.data+5, pktout.length-5,
+                                      &newpayload, &newlen)) {
+           pktout.length = 5;
+           ssh2_pkt_adddata(newpayload, newlen);
+           free(newpayload);
+       }
+    }
+
+    /*
      * Add padding. At least four bytes, and must also bring total
      * length (minus MAC) up to a multiple of the block size.
      */
@@ -907,6 +1025,7 @@ static int do_ssh_init(unsigned char c) {
            break;
     }
 
+    ssh_agentfwd_enabled = FALSE;
     rdpkt2_state.incoming_sequence = 0;
 
     *vsp = 0;
@@ -1255,20 +1374,20 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            c_write("\r\n", 2);
            username[strcspn(username, "\n\r")] = '\0';
        } else {
-           char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-            if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
-               sprintf(stuff, "Sent username \"%s\".\r\n", username);
-                c_write(stuff, strlen(stuff));
-           }
        }
 
        send_packet(SSH1_CMSG_USER, PKT_STR, username, PKT_END);
        {
-           char userlog[20+sizeof(username)];
+           char userlog[22+sizeof(username)];
            sprintf(userlog, "Sent username \"%s\"", username);
            logevent(userlog);
+            if (flags & FLAG_INTERACTIVE &&
+                (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
+               strcat(userlog, "\r\n");
+                c_write(userlog, strlen(userlog));
+           }
        }
     }
 
@@ -1615,8 +1734,10 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
             crReturnV;
         } else if (pktin.type == SSH1_SMSG_FAILURE) {
             logevent("Agent forwarding refused");
-        } else
+        } else {
             logevent("Agent forwarding enabled");
+           ssh_agentfwd_enabled = TRUE;
+       }
     }
 
     if (!cfg.nopty) {
@@ -1637,6 +1758,21 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
        logevent("Allocated pty");
     }
 
+    if (cfg.compression) {
+        send_packet(SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
+        do { crReturnV; } while (!ispkt);
+        if (pktin.type != SSH1_SMSG_SUCCESS && pktin.type != SSH1_SMSG_FAILURE) {
+            bombout(("Protocol confusion"));
+            crReturnV;
+        } else if (pktin.type == SSH1_SMSG_FAILURE) {
+            c_write("Server refused to compress\r\n", 32);
+        }
+       logevent("Started compression");
+       ssh1_compressing = TRUE;
+       zlib_compress_init();
+       zlib_decompress_init();
+    }
+
     if (*cfg.remote_cmd)
         send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cfg.remote_cmd, PKT_END);
     else
@@ -1665,26 +1801,35 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
             } else if (pktin.type == SSH1_SMSG_AGENT_OPEN) {
                 /* Remote side is trying to open a channel to talk to our
                  * agent. Give them back a local channel number. */
-                unsigned i = 1;
+                unsigned i;
                 struct ssh_channel *c;
                 enum234 e;
-                for (c = first234(ssh_channels, &e); c; c = next234(&e)) {
-                    if (c->localid > i)
-                        break;         /* found a free number */
-                    i = c->localid + 1;
-                }
-                c = malloc(sizeof(struct ssh_channel));
-                c->remoteid = GET_32BIT(pktin.body);
-                c->localid = i;
-                c->closes = 0;
-                c->type = SSH1_SMSG_AGENT_OPEN;   /* identify channel type */
-                c->u.a.lensofar = 0;
-                add234(ssh_channels, c);
-                send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
-                            PKT_INT, c->remoteid, PKT_INT, c->localid,
-                            PKT_END);
-            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
-                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
+
+               /* Refuse if agent forwarding is disabled. */
+               if (!ssh_agentfwd_enabled) {
+                   send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                               PKT_INT, GET_32BIT(pktin.body),
+                               PKT_END);
+               } else {
+                   i = 1;
+                   for (c = first234(ssh_channels, &e); c; c = next234(&e)) {
+                       if (c->localid > i)
+                           break;     /* found a free number */
+                       i = c->localid + 1;
+                   }
+                   c = malloc(sizeof(struct ssh_channel));
+                   c->remoteid = GET_32BIT(pktin.body);
+                   c->localid = i;
+                   c->closes = 0;
+                   c->type = SSH1_SMSG_AGENT_OPEN;/* identify channel type */
+                   c->u.a.lensofar = 0;
+                   add234(ssh_channels, c);
+                   send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                               PKT_INT, c->remoteid, PKT_INT, c->localid,
+                               PKT_END);
+               }
+           } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
+                      pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
                 /* Remote side closes a channel. */
                 unsigned i = GET_32BIT(pktin.body);
                 struct ssh_channel *c;
@@ -1842,15 +1987,17 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     static const struct ssh_compress *sccomp_tobe = NULL;
     static char *hostkeydata, *sigdata, *keystr, *fingerprint;
     static int hostkeylen, siglen;
+    static void *hkey;                /* actual host key */
     static unsigned char exchange_hash[20];
     static unsigned char keyspace[40];
     static const struct ssh_cipher *preferred_cipher;
+    static const struct ssh_compress *preferred_comp;
 
     crBegin;
     random_init();
 
     /*
-     * Set up the preferred cipher.
+     * Set up the preferred cipher and compression.
      */
     if (cfg.cipher == CIPHER_BLOWFISH) {
         preferred_cipher = &ssh_blowfish_ssh2;
@@ -1863,6 +2010,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
         /* Shouldn't happen, but we do want to initialise to _something_. */
         preferred_cipher = &ssh_3des_ssh2;
     }
+    if (cfg.compression)
+       preferred_comp = &ssh_zlib;
+    else
+       preferred_comp = &ssh_comp_none;
 
     /*
      * Be prepared to work around the buggy MAC problem.
@@ -1925,16 +2076,18 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
     /* List client->server compression algorithms. */
     ssh2_pkt_addstring_start();
-    for (i = 0; i < lenof(compressions); i++) {
-        ssh2_pkt_addstring_str(compressions[i]->name);
-        if (i < lenof(compressions)-1)
+    for (i = 0; i < lenof(compressions)+1; i++) {
+        const struct ssh_compress *c = i==0 ? preferred_comp : compressions[i-1];
+        ssh2_pkt_addstring_str(c->name);
+        if (i < lenof(compressions))
             ssh2_pkt_addstring_str(",");
     }
     /* List server->client compression algorithms. */
     ssh2_pkt_addstring_start();
-    for (i = 0; i < lenof(compressions); i++) {
-        ssh2_pkt_addstring_str(compressions[i]->name);
-        if (i < lenof(compressions)-1)
+    for (i = 0; i < lenof(compressions)+1; i++) {
+        const struct ssh_compress *c = i==0 ? preferred_comp : compressions[i-1];
+        ssh2_pkt_addstring_str(c->name);
+        if (i < lenof(compressions))
             ssh2_pkt_addstring_str(",");
     }
     /* List client->server languages. Empty list. */
@@ -2007,16 +2160,18 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
         }
     }
     ssh2_pkt_getstring(&str, &len);    /* client->server compression */
-    for (i = 0; i < lenof(compressions); i++) {
-        if (in_commasep_string(compressions[i]->name, str, len)) {
-            cscomp_tobe = compressions[i];
+    for (i = 0; i < lenof(compressions)+1; i++) {
+        const struct ssh_compress *c = i==0 ? preferred_comp : compressions[i-1];
+        if (in_commasep_string(c->name, str, len)) {
+            cscomp_tobe = c;
             break;
         }
     }
     ssh2_pkt_getstring(&str, &len);    /* server->client compression */
-    for (i = 0; i < lenof(compressions); i++) {
-        if (in_commasep_string(compressions[i]->name, str, len)) {
-            sccomp_tobe = compressions[i];
+    for (i = 0; i < lenof(compressions)+1; i++) {
+        const struct ssh_compress *c = i==0 ? preferred_comp : compressions[i-1];
+        if (in_commasep_string(c->name, str, len)) {
+            sccomp_tobe = c;
             break;
         }
     }
@@ -2062,8 +2217,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     debug(("\r\n"));
 #endif
 
-    hostkey->setkey(hostkeydata, hostkeylen);
-    if (!hostkey->verifysig(sigdata, siglen, exchange_hash, 20)) {
+    hkey = hostkey->newkey(hostkeydata, hostkeylen);
+    if (!hostkey->verifysig(hkey, sigdata, siglen, exchange_hash, 20)) {
         bombout(("Server failed host key check"));
         crReturn(0);
     }
@@ -2081,14 +2236,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      * Authenticate remote host: verify host key. (We've already
      * checked the signature of the exchange hash.)
      */
-    keystr = hostkey->fmtkey();
-    fingerprint = hostkey->fingerprint();
+    keystr = hostkey->fmtkey(hkey);
+    fingerprint = hostkey->fingerprint(hkey);
     verify_ssh_host_key(savedhost, savedport, hostkey->keytype,
                         keystr, fingerprint);
     logevent("Host key fingerprint is:");
     logevent(fingerprint);
     free(fingerprint);
     free(keystr);
+    hostkey->freekey(hkey);
 
     /*
      * Send SSH2_MSG_NEWKEYS.
@@ -2105,6 +2261,8 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     scmac = scmac_tobe;
     cscomp = cscomp_tobe;
     sccomp = sccomp_tobe;
+    cscomp->compress_init();
+    sccomp->decompress_init();
     /*
      * Set IVs after keys.
      */
@@ -2537,7 +2695,7 @@ static char *ssh_init (char *host, int port, char **realhost) {
  * Called to send data down the Telnet connection.
  */
 static void ssh_send (char *buf, int len) {
-    if (s == NULL)
+    if (s == NULL || ssh_protocol == NULL)
        return;
 
     ssh_protocol(buf, len, 0);