]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - ssh.c
Log when we avoid using an unknown host key.
[PuTTY.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index 288616fd901c6756bac358f98e5618220ed9f283..ee9798af5e6d6485440afc544a8dc9c25968c416 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -11,6 +11,7 @@
 
 #include "putty.h"
 #include "tree234.h"
+#include "storage.h"
 #include "ssh.h"
 #ifndef NO_GSSAPI
 #include "sshgssc.h"
@@ -792,6 +793,7 @@ struct ssh_tag {
     int send_ok;
     int echoing, editing;
 
+    int session_started;
     void *frontend;
 
     int ospeed, ispeed;                       /* temporaries */
@@ -953,6 +955,24 @@ struct ssh_tag {
      */
     struct ssh_gss_liblist *gsslibs;
 #endif
+
+    /*
+     * The last list returned from get_specials.
+     */
+    struct telnet_special *specials;
+
+    /*
+     * List of host key algorithms for which we _don't_ have a stored
+     * host key. These are indices into the main hostkey_algs[] array
+     */
+    int uncert_hostkeys[lenof(hostkey_algs)];
+    int n_uncert_hostkeys;
+
+    /*
+     * Flag indicating that the current rekey is intended to finish
+     * with a newly cross-certified host key.
+     */
+    int cross_certifying;
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -3070,6 +3090,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
        crReturn(1);
     }
 
+    ssh->session_started = TRUE;
+
     s->vstrsize = sizeof(protoname) + 16;
     s->vstring = snewn(s->vstrsize, char);
     strcpy(s->vstring, protoname);
@@ -3452,34 +3474,20 @@ static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port,
                            const char *error_msg, int error_code)
 {
     Ssh ssh = (Ssh) plug;
-    char addrbuf[256], *msg;
-
-    if (ssh->attempting_connshare) {
-        /*
-         * While we're attempting connection sharing, don't loudly log
-         * everything that happens. Real TCP connections need to be
-         * logged when we _start_ trying to connect, because it might
-         * be ages before they respond if something goes wrong; but
-         * connection sharing is local and quick to respond, and it's
-         * sufficient to simply wait and see whether it worked
-         * afterwards.
-         */
-    } else {
-        sk_getaddr(addr, addrbuf, lenof(addrbuf));
 
-        if (type == 0) {
-            if (sk_addr_needs_port(addr)) {
-                msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-            } else {
-                msg = dupprintf("Connecting to %s", addrbuf);
-            }
-        } else {
-            msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
-        }
+    /*
+     * While we're attempting connection sharing, don't loudly log
+     * everything that happens. Real TCP connections need to be logged
+     * when we _start_ trying to connect, because it might be ages
+     * before they respond if something goes wrong; but connection
+     * sharing is local and quick to respond, and it's sufficient to
+     * simply wait and see whether it worked afterwards.
+     */
 
-        logevent(msg);
-        sfree(msg);
-    }
+    if (!ssh->attempting_connshare)
+        backend_socket_log(ssh->frontend, type, addr, port,
+                           error_msg, error_code, ssh->conf,
+                           ssh->session_started);
 }
 
 void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
@@ -3674,10 +3682,8 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port,
          * Try to find host.
          */
         addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
-        logeventf(ssh, "Looking up host \"%s\"%s", host,
-                  (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-                   (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
-        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+                           ssh->frontend, "SSH connection");
         if ((err = sk_addr_error(addr)) != NULL) {
             sk_addr_free(addr);
             return err;
@@ -5575,7 +5581,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
     ssh_pkt_getstring(pktin, &host, &hostsize);
     port = ssh_pkt_getuint32(pktin);
 
-    pf.dhost = dupprintf("%.*s", hostsize, host);
+    pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host));
     pf.dport = port;
     pfp = find234(ssh->rportfwds, &pf, NULL);
 
@@ -6058,7 +6064,7 @@ static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin)
     int msglen;
 
     ssh_pkt_getstring(pktin, &msg, &msglen);
-    logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+    logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
 }
 
 static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
@@ -6068,7 +6074,8 @@ static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
     int msglen;
 
     ssh_pkt_getstring(pktin, &msg, &msglen);
-    bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+    bombout(("Server sent disconnect message:\n\"%.*s\"",
+             msglen, NULLTOEMPTY(msg)));
 }
 
 static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
@@ -6678,10 +6685,36 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                    in_commasep_string(alg->u.comp->delayed_name, str, len))
                    s->pending_compression = TRUE;  /* try this later */
            }
-           bombout(("Couldn't agree a %s ((available: %.*s)",
+           bombout(("Couldn't agree a %s (available: %.*s)",
                     kexlist_descr[i], len, str));
            crStopV;
          matched:;
+
+            if (i == KEXLIST_HOSTKEY) {
+                int j;
+
+                /*
+                 * In addition to deciding which host key we're
+                 * actually going to use, we should make a list of the
+                 * host keys offered by the server which we _don't_
+                 * have cached. These will be offered as cross-
+                 * certification options by ssh_get_specials.
+                 *
+                 * We also count the key we're currently using for KEX
+                 * as one we've already got, because by the time this
+                 * menu becomes visible, it will be.
+                 */
+                ssh->n_uncert_hostkeys = 0;
+
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j] != ssh->hostkey &&
+                        in_commasep_string(hostkey_algs[j]->name, str, len) &&
+                        !have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                           hostkey_algs[j]->keytype)) {
+                        ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
+                    }
+                }
+            }
        }
 
        if (s->pending_compression) {
@@ -7126,8 +7159,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
     dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
 #endif
 
-    if (!s->hkey ||
-       !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+    if (!s->hkey) {
+       bombout(("Server's host key is invalid"));
+       crStopV;
+    }
+
+    if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
                                 (char *)s->exchange_hash,
                                 ssh->kex->hash->hlen)) {
 #ifndef FUZZING
@@ -7138,6 +7175,46 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
     if (!s->got_session_id) {
+       /*
+        * Make a note of any host key format we'd have preferred to use,
+        * had we already known the corresponding keys.
+        */
+       {
+           int i, j = 0;
+           char *list = NULL;
+           for (i = 0; i < lenof(hostkey_algs); i++) {
+               if (hostkey_algs[i] == ssh->hostkey)
+                   /* Not worth mentioning key types we wouldn't use */
+                   break;
+               else if (ssh->uncert_hostkeys[j] == i) {
+                   char *newlist;
+                   if (list)
+                       newlist = dupprintf("%s/%s", list,
+                                           hostkey_algs[i]->name);
+                   else
+                       newlist = dupprintf("%s", hostkey_algs[i]->name);
+                   sfree(list);
+                   list = newlist;
+                   j++;
+                   /* Assumes that hostkey_algs and uncert_hostkeys are
+                    * sorted in the same order */
+                   if (j == ssh->n_uncert_hostkeys)
+                       break;
+                   else
+                       assert(ssh->uncert_hostkeys[j] >
+                              ssh->uncert_hostkeys[j-1]);
+               }
+           }
+           if (list) {
+               logeventf(ssh,
+                         "Server has %s host key%s, but we don't know %s; "
+                         "using %s instead",
+                         list, j ? "s" : "", j ? "any of them" : "it",
+                         ssh->hostkey->name);
+               sfree(list);
+           }
+       }
+
         /*
          * Authenticate remote host: verify host key. (We've already
          * checked the signature of the exchange hash.)
@@ -7185,6 +7262,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
          * subsequent rekeys.
          */
         ssh->hostkey_str = s->keystr;
+    } else if (ssh->cross_certifying) {
+        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
+        logevent("Storing additional host key for this host:");
+        logevent(s->fingerprint);
+        store_host_key(ssh->savedhost, ssh->savedport,
+                       ssh->hostkey->keytype, s->keystr);
+        ssh->cross_certifying = FALSE;
+        /*
+         * Don't forget to store the new key as the one we'll be
+         * re-checking in future normal rekeys.
+         */
+        ssh->hostkey_str = s->keystr;
     } else {
         /*
          * In a rekey, we never present an interactive host key
@@ -7308,14 +7397,14 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
      */
     if (ssh->sc_cipher_ctx)
        ssh->sccipher->free_context(ssh->sc_cipher_ctx);
-    if (ssh->sccipher) {
+    if (s->sccipher_tobe) {
        ssh->sccipher = s->sccipher_tobe;
        ssh->sc_cipher_ctx = ssh->sccipher->make_context();
     }
 
     if (ssh->sc_mac_ctx)
        ssh->scmac->free_context(ssh->sc_mac_ctx);
-    if (ssh->scmac) {
+    if (s->scmac_tobe) {
        ssh->scmac = s->scmac_tobe;
        ssh->scmac_etm = s->scmac_etm_tobe;
        ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
@@ -7371,6 +7460,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
      */
     freebn(s->K);
 
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    update_specials_menu(ssh->frontend);
+
     /*
      * Key exchange is over. Loop straight back round if we have a
      * deferred rekey reason.
@@ -8273,7 +8368,8 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
             reason_code = 0; /* ensure reasons[reason_code] in range */
         ssh_pkt_getstring(pktin, &reason_string, &reason_length);
         logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
-                  reasons[reason_code], reason_length, reason_string);
+                  reasons[reason_code], reason_length,
+                  NULLTOEMPTY(reason_string));
 
         pfd_close(c->u.pfd.pf);
     } else if (c->type == CHAN_ZOMBIE) {
@@ -8569,9 +8665,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        char *addrstr;
 
        ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
-       addrstr = snewn(peeraddrlen+1, char);
-       memcpy(addrstr, peeraddr, peeraddrlen);
-       addrstr[peeraddrlen] = '\0';
+       addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr));
        peerport = ssh_pkt_getuint32(pktin);
 
        logeventf(ssh, "Received X11 connect request from %s:%d",
@@ -8606,13 +8700,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
        char *shost;
        int shostlen;
        ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
-        pf.shost = dupprintf("%.*s", shostlen, shost);
+        pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost));
        pf.sport = ssh_pkt_getuint32(pktin);
        ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
        peerport = ssh_pkt_getuint32(pktin);
        realpf = find234(ssh->rportfwds, &pf, NULL);
        logeventf(ssh, "Received remote port %s:%d open request "
-                 "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+                 "from %.*s:%d", pf.shost, pf.sport,
+                  peeraddrlen, NULLTOEMPTY(peeraddr), peerport);
         sfree(pf.shost);
 
        if (realpf == NULL) {
@@ -10276,7 +10371,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                    s->cur_prompt->to_server = TRUE;
                    s->cur_prompt->name = dupstr("New SSH password");
                    s->cur_prompt->instruction =
-                       dupprintf("%.*s", prompt_len, prompt);
+                       dupprintf("%.*s", prompt_len, NULLTOEMPTY(prompt));
                    s->cur_prompt->instr_reqd = TRUE;
                    /*
                     * There's no explicit requirement in the protocol
@@ -10714,13 +10809,13 @@ static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin)
     logevent(buf);
     sfree(buf);
     buf = dupprintf("Disconnection message text: %.*s",
-                   msglen, msg);
+                   msglen, NULLTOEMPTY(msg));
     logevent(buf);
     bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
             reason,
             (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
             ssh2_disconnect_reasons[reason] : "unknown",
-            msglen, msg));
+            msglen, NULLTOEMPTY(msg)));
     sfree(buf);
 }
 
@@ -10734,7 +10829,7 @@ static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
     ssh2_pkt_getbool(pktin);
     ssh_pkt_getstring(pktin, &msg, &msglen);
 
-    logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+    logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
 }
 
 static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
@@ -11011,6 +11106,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->X11_fwd_enabled = FALSE;
     ssh->connshare = NULL;
     ssh->attempting_connshare = FALSE;
+    ssh->session_started = FALSE;
+    ssh->specials = NULL;
+    ssh->n_uncert_hostkeys = 0;
+    ssh->cross_certifying = FALSE;
 
     *backend_handle = ssh;
 
@@ -11158,6 +11257,7 @@ static void ssh_free(void *handle)
     sfree(ssh->v_s);
     sfree(ssh->fullhostname);
     sfree(ssh->hostkey_str);
+    sfree(ssh->specials);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
@@ -11371,19 +11471,24 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     static const struct telnet_special specials_end[] = {
        {NULL, TS_EXITMENU}
     };
-    /* XXX review this length for any changes: */
-    static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
-                                             lenof(ssh2_rekey_special) +
-                                             lenof(ssh2_session_specials) +
-                                             lenof(specials_end)];
+
+    struct telnet_special *specials = NULL;
+    int nspecials = 0, specialsize = 0;
+
     Ssh ssh = (Ssh) handle;
-    int i = 0;
-#define ADD_SPECIALS(name) \
-    do { \
-       assert((i + lenof(name)) <= lenof(ssh_specials)); \
-       memcpy(&ssh_specials[i], name, sizeof name); \
-       i += lenof(name); \
-    } while(0)
+
+    sfree(ssh->specials);
+
+#define ADD_SPECIALS(name) do                                           \
+    {                                                                   \
+        int len = lenof(name);                                          \
+        if (nspecials + len > specialsize) {                            \
+            specialsize = (nspecials + len) * 5 / 4 + 32;               \
+            specials = sresize(specials, specialsize, struct telnet_special); \
+        }                                                               \
+       memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
+        nspecials += len;                                               \
+    } while (0)
 
     if (ssh->version == 1) {
        /* Don't bother offering IGNORE if we've decided the remote
@@ -11398,11 +11503,37 @@ static const struct telnet_special *ssh_get_specials(void *handle)
            ADD_SPECIALS(ssh2_rekey_special);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
+
+        if (ssh->n_uncert_hostkeys) {
+            static const struct telnet_special uncert_start[] = {
+                {NULL, TS_SEP},
+                {"Cache new host key type", TS_SUBMENU},
+            };
+            static const struct telnet_special uncert_end[] = {
+                {NULL, TS_EXITMENU},
+            };
+            int i;
+
+            ADD_SPECIALS(uncert_start);
+            for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
+                struct telnet_special uncert[1];
+                const struct ssh_signkey *alg =
+                    hostkey_algs[ssh->uncert_hostkeys[i]];
+                uncert[0].name = alg->name;
+                uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
+                ADD_SPECIALS(uncert);
+            }
+            ADD_SPECIALS(uncert_end);
+        }
     } /* else we're not ready yet */
 
-    if (i) {
+    if (nspecials)
        ADD_SPECIALS(specials_end);
-       return ssh_specials;
+
+    ssh->specials = specials;
+
+    if (nspecials) {
+        return specials;
     } else {
        return NULL;
     }
@@ -11454,6 +11585,13 @@ static void ssh_special(void *handle, Telnet_Special code)
             ssh->version == 2) {
            do_ssh2_transport(ssh, "at user request", -1, NULL);
        }
+    } else if (code >= TS_LOCALSTART) {
+        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART];
+        ssh->cross_certifying = TRUE;
+       if (!ssh->kex_in_progress && !ssh->bare_connection &&
+            ssh->version == 2) {
+           do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
+       }
     } else if (code == TS_BRK) {
        if (ssh->state == SSH_STATE_CLOSED
            || ssh->state == SSH_STATE_PREPACKET) return;