]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - ssh.c
Fix handle leak in winhandl.c.
[PuTTY.git] / ssh.c
diff --git a/ssh.c b/ssh.c
index 4eb84f899706a1ccd8bacf574572565d4243af0d..a7f5882f7dff2f87319578f8c559a884174e53cc 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -75,6 +75,7 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_MAXPKT                                256
 #define BUG_CHOKES_ON_SSH2_IGNORE               512
 #define BUG_CHOKES_ON_WINADJ                   1024
+#define BUG_SENDS_LATE_REQUEST_REPLY           2048
 
 /*
  * Codes for terminal modes.
@@ -2812,6 +2813,19 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
        ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
        logevent("We believe remote version has winadj bug");
     }
+
+    if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON ||
+       (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == AUTO &&
+        (wc_match("OpenSSH_[2-5].*", imp) ||
+         wc_match("OpenSSH_6.[0-6]*", imp)))) {
+       /*
+        * These versions have the SSH-2 channel request bug. 6.7 and
+        * above do not:
+        * https://bugzilla.mindrot.org/show_bug.cgi?id=1818
+        */
+       ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
+       logevent("We believe remote version has SSH-2 channel request bug");
+    }
 }
 
 /*
@@ -2946,11 +2960,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
 
     if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
-       bombout(("SSH protocol version 1 required by user but not provided by server"));
+       bombout(("SSH protocol version 1 required by configuration but "
+                "not provided by server"));
        crStop(0);
     }
     if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
-       bombout(("SSH protocol version 2 required by user but not provided by server"));
+       bombout(("SSH protocol version 2 required by configuration but "
+                "not provided by server"));
        crStop(0);
     }
 
@@ -3271,6 +3287,14 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
        ssh->portfwds = NULL;
     }
 
+    /*
+     * Also stop attempting to connection-share.
+     */
+    if (ssh->connshare) {
+        sharestate_free(ssh->connshare);
+        ssh->connshare = NULL;
+    }
+
     return ret;
 }
 
@@ -3663,6 +3687,59 @@ static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
     sfree(error);
 }
 
+int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint,
+                               const struct ssh_signkey *ssh2keytype,
+                               void *ssh2keydata)
+{
+    if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) {
+        return -1;                     /* no manual keys configured */
+    }
+
+    if (fingerprint) {
+        /*
+         * The fingerprint string we've been given will have things
+         * like 'ssh-rsa 2048' at the front of it. Strip those off and
+         * narrow down to just the colon-separated hex block at the
+         * end of the string.
+         */
+        const char *p = strrchr(fingerprint, ' ');
+        fingerprint = p ? p+1 : fingerprint;
+        /* Quick sanity checks, including making sure it's in lowercase */
+        assert(strlen(fingerprint) == 16*3 - 1);
+        assert(fingerprint[2] == ':');
+        assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
+
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 fingerprint))
+            return 1;                  /* success */
+    }
+
+    if (ssh2keydata) {
+        /*
+         * Construct the base64-encoded public key blob and see if
+         * that's listed.
+         */
+        unsigned char *binblob;
+        char *base64blob;
+        int binlen, atoms, i;
+        binblob = ssh2keytype->public_blob(ssh2keydata, &binlen);
+        atoms = (binlen + 2) / 3;
+        base64blob = snewn(atoms * 4 + 1, char);
+        for (i = 0; i < atoms; i++)
+            base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i);
+        base64blob[atoms * 4] = '\0';
+        sfree(binblob);
+        if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+                                 base64blob)) {
+            sfree(base64blob);
+            return 1;                  /* success */
+        }
+        sfree(base64blob);
+    }
+
+    return 0;
+}
+
 /*
  * Handle the key exchange and user authentication phases.
  */
@@ -3786,29 +3863,39 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        rsastr_fmt(keystr, &s->hostkey);
        rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
 
-        ssh_set_frozen(ssh, 1);
-       s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                        ssh->savedhost, ssh->savedport,
-                                        "rsa", keystr, fingerprint,
-                                        ssh_dialog_callback, ssh);
-       sfree(keystr);
-        if (s->dlgret < 0) {
-            do {
-                crReturn(0);
-                if (pktin) {
-                    bombout(("Unexpected data from server while waiting"
-                             " for user host key response"));
-                    crStop(0);
-                }
-            } while (pktin || inlen > 0);
-            s->dlgret = ssh->user_response;
-        }
-        ssh_set_frozen(ssh, 0);
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            sfree(keystr);
+            crStop(0);
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            "rsa", keystr, fingerprint,
+                                            ssh_dialog_callback, ssh);
+            sfree(keystr);
+            if (s->dlgret < 0) {
+                do {
+                    crReturn(0);
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStop(0);
+                    }
+                } while (pktin || inlen > 0);
+                s->dlgret = ssh->user_response;
+            }
+            ssh_set_frozen(ssh, 0);
 
-        if (s->dlgret == 0) {
-           ssh_disconnect(ssh, "User aborted at host key verification",
-                          NULL, 0, TRUE);
-           crStop(0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "User aborted at host key verification",
+                               NULL, 0, TRUE);
+                crStop(0);
+            }
+        } else {
+            sfree(keystr);
         }
     }
 
@@ -6212,6 +6299,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
        pktin->savedpos += 16;          /* skip garbage cookie */
        ssh_pkt_getstring(pktin, &str, &len);    /* key exchange algorithms */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
 
        preferred = NULL;
        for (i = 0; i < s->n_preferred_kex; i++) {
@@ -6231,8 +6322,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!ssh->kex) {
-           bombout(("Couldn't agree a key exchange algorithm (available: %s)",
-                    str ? str : "(null)"));
+            bombout(("Couldn't agree a key exchange algorithm"
+                     " (available: %.*s)", len, str));
            crStopV;
        }
        /*
@@ -6242,6 +6333,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         */
        s->guessok = first_in_commasep_string(preferred, str, len);
        ssh_pkt_getstring(pktin, &str, &len);    /* host key algorithms */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(hostkey_algs); i++) {
            if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
                ssh->hostkey = hostkey_algs[i];
@@ -6249,14 +6344,18 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        if (!ssh->hostkey) {
-           bombout(("Couldn't agree a host key algorithm (available: %s)",
-                    str ? str : "(null)"));
+            bombout(("Couldn't agree a host key algorithm"
+                     " (available: %.*s)", len, str));
            crStopV;
        }
 
        s->guessok = s->guessok &&
            first_in_commasep_string(hostkey_algs[0]->name, str, len);
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server cipher */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) {
@@ -6273,12 +6372,16 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!s->cscipher_tobe) {
-           bombout(("Couldn't agree a client-to-server cipher (available: %s)",
-                    str ? str : "(null)"));
+            bombout(("Couldn't agree a client-to-server cipher"
+                     " (available: %.*s)", len, str));
            crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client cipher */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) {
@@ -6295,12 +6398,16 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                break;
        }
        if (!s->sccipher_tobe) {
-           bombout(("Couldn't agree a server-to-client cipher (available: %s)",
-                    str ? str : "(null)"));
+            bombout(("Couldn't agree a server-to-client cipher"
+                     " (available: %.*s)", len, str));
            crStopV;
        }
 
        ssh_pkt_getstring(pktin, &str, &len);    /* client->server mac */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->csmac_tobe = s->maclist[i];
@@ -6308,6 +6415,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        ssh_pkt_getstring(pktin, &str, &len);    /* server->client mac */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < s->nmacs; i++) {
            if (in_commasep_string(s->maclist[i]->name, str, len)) {
                s->scmac_tobe = s->maclist[i];
@@ -6315,6 +6426,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        ssh_pkt_getstring(pktin, &str, &len);  /* client->server compression */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(compressions) + 1; i++) {
            const struct ssh_compress *c =
                i == 0 ? s->preferred_comp : compressions[i - 1];
@@ -6331,6 +6446,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
        }
        ssh_pkt_getstring(pktin, &str, &len);  /* server->client compression */
+        if (!str) {
+            bombout(("KEXINIT packet was incomplete"));
+            crStopV;
+        }
        for (i = 0; i < lenof(compressions) + 1; i++) {
            const struct ssh_compress *c =
                i == 0 ? s->preferred_comp : compressions[i - 1];
@@ -6675,31 +6794,39 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * checked the signature of the exchange hash.)
          */
         s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
-        ssh_set_frozen(ssh, 1);
-        s->dlgret = verify_ssh_host_key(ssh->frontend,
-                                        ssh->savedhost, ssh->savedport,
-                                        ssh->hostkey->keytype, s->keystr,
-                                        s->fingerprint,
-                                        ssh_dialog_callback, ssh);
-        if (s->dlgret < 0) {
-            do {
-                crReturnV;
-                if (pktin) {
-                    bombout(("Unexpected data from server while waiting"
-                             " for user host key response"));
-                    crStopV;
-                }
-            } while (pktin || inlen > 0);
-            s->dlgret = ssh->user_response;
-        }
-        ssh_set_frozen(ssh, 0);
-        if (s->dlgret == 0) {
-            ssh_disconnect(ssh, "User aborted at host key verification", NULL,
-                           0, TRUE);
-            crStopV;
-        }
         logevent("Host key fingerprint is:");
         logevent(s->fingerprint);
+        /* First check against manually configured host keys. */
+        s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint,
+                                               ssh->hostkey, s->hkey);
+        if (s->dlgret == 0) {          /* did not match */
+            bombout(("Host key did not appear in manually configured list"));
+            crStopV;
+        } else if (s->dlgret < 0) { /* none configured; use standard handling */
+            ssh_set_frozen(ssh, 1);
+            s->dlgret = verify_ssh_host_key(ssh->frontend,
+                                            ssh->savedhost, ssh->savedport,
+                                            ssh->hostkey->keytype, s->keystr,
+                                            s->fingerprint,
+                                            ssh_dialog_callback, ssh);
+            if (s->dlgret < 0) {
+                do {
+                    crReturnV;
+                    if (pktin) {
+                        bombout(("Unexpected data from server while waiting"
+                                 " for user host key response"));
+                        crStopV;
+                    }
+                } while (pktin || inlen > 0);
+                s->dlgret = ssh->user_response;
+            }
+            ssh_set_frozen(ssh, 0);
+            if (s->dlgret == 0) {
+                ssh_disconnect(ssh, "Aborted at host key verification", NULL,
+                               0, TRUE);
+                crStopV;
+            }
+        }
         sfree(s->fingerprint);
         /*
          * Save this host key, to check against the one presented in
@@ -7119,10 +7246,11 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
  * request-specific data added and be sent.  Note that if a handler is
  * provided, it's essential that the request actually be sent.
  *
- * The handler will usually be passed the response packet in pktin.
- * If pktin is NULL, this means that no reply will ever be forthcoming
- * (e.g. because the entire connection is being destroyed) and the
- * handler should free any storage it's holding.
+ * The handler will usually be passed the response packet in pktin. If
+ * pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed, or because
+ * the server initiated channel closure before we saw the response)
+ * and the handler should free any storage it's holding.
  */
 static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
                                        cchandler_fn_t handler, void *ctx)
@@ -7612,6 +7740,21 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
      */
     ssh2_channel_got_eof(c);
 
+    if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
+        /*
+         * It also means we stop expecting to see replies to any
+         * outstanding channel requests, so clean those up too.
+         * (ssh_chanreq_init will enforce by assertion that we don't
+         * subsequently put anything back on this list.)
+         */
+        while (c->v.v2.chanreq_head) {
+            struct outstanding_channel_request *ocr = c->v.v2.chanreq_head;
+            ocr->handler(c, NULL, ocr->ctx);
+            c->v.v2.chanreq_head = ocr->next;
+            sfree(ocr);
+        }
+    }
+
     /*
      * And we also send an outgoing EOF, if we haven't already, on the
      * assumption that CLOSE is a pretty forceful announcement that
@@ -7787,6 +7930,16 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
     ssh_pkt_getstring(pktin, &type, &typelen);
     want_reply = ssh2_pkt_getbool(pktin);
 
+    if (c->closes & CLOSES_SENT_CLOSE) {
+        /*
+         * We don't reply to channel requests after we've sent
+         * CHANNEL_CLOSE for the channel, because our reply might
+         * cross in the network with the other side's CHANNEL_CLOSE
+         * and arrive after they have wound the channel up completely.
+         */
+        want_reply = FALSE;
+    }
+
     /*
      * Having got the channel number, we now look at
      * the request type string to see if it's something
@@ -8416,7 +8569,8 @@ static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin)
 static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
                                   void *ctx)
 {
-    do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+    if (pktin)
+        do_ssh2_authconn(c->ssh, NULL, 0, pktin);
 }
 
 static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
@@ -10444,6 +10598,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->sent_console_eof = FALSE;
     ssh->got_pty = FALSE;
     ssh->bare_connection = FALSE;
+    ssh->X11_fwd_enabled = FALSE;
+    ssh->connshare = NULL;
     ssh->attempting_connshare = FALSE;
 
     *backend_handle = ssh;
@@ -10484,11 +10640,13 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->gsslibs = NULL;
 #endif
 
+    random_ref(); /* do this now - may be needed by sharing setup code */
+
     p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
-    if (p != NULL)
+    if (p != NULL) {
+        random_unref();
        return p;
-
-    random_ref();
+    }
 
     return NULL;
 }
@@ -11096,13 +11254,19 @@ static int ssh_return_exitcode(void *handle)
 }
 
 /*
- * cfg_info for SSH is the currently running version of the
- * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ * cfg_info for SSH is the protocol running in this session.
+ * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare
+ * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.)
  */
 static int ssh_cfg_info(void *handle)
 {
     Ssh ssh = (Ssh) handle;
-    return ssh->version;
+    if (ssh->version == 0)
+       return 0; /* don't know yet */
+    else if (ssh->bare_connection)
+       return -1;
+    else
+       return ssh->version;
 }
 
 /*