From a61694c69f0cff888fa4f5ff1c1cfedd2cc30fce Mon Sep 17 00:00:00 2001
From: Simon Tatham <anakin@pobox.com>
Date: Sun, 10 Dec 2023 15:09:50 +0000
Subject: Remove fatal-error reporting from scan_kexinits.

This will allow it to be called in a second circumstance where we're
trying to find out whether something _would_ have worked, so that we
never want to terminate the connection.

Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commit;h=fdc891d17063ab26cf68c74245ab1fd9771556cb
Last-Update: 2023-12-18

Patch-Name: remove-fatal-error-reporting-from-scan_kexinit.patch
---
 ssh/transport2.c | 83 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 64 insertions(+), 19 deletions(-)

diff --git a/ssh/transport2.c b/ssh/transport2.c
index b6dc4a47..4ba40b52 100644
--- a/ssh/transport2.c
+++ b/ssh/transport2.c
@@ -1002,13 +1002,27 @@ static bool kexinit_keyword_found(ptrlen list, ptrlen keyword)
     return false;
 }
 
-static bool ssh2_scan_kexinits(
+typedef struct ScanKexinitsResult {
+    bool success;
+
+    /* only if success is false */
+    enum {
+        SKR_INCOMPLETE,
+        SKR_UNKNOWN_ID,
+        SKR_NO_AGREEMENT,
+    } error;
+
+    const char *kind; /* what kind of thing did we fail to sort out? */
+    ptrlen desc;      /* and what was it? or what was the available list? */
+} ScanKexinitsResult;
+
+static ScanKexinitsResult ssh2_scan_kexinits(
     ptrlen client_kexinit, ptrlen server_kexinit, bool we_are_server,
     struct kexinit_algorithm_list kexlists[NKEXLIST],
     const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
     transport_direction *cs, transport_direction *sc,
     bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
-    Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
+    bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
     struct server_hostkeys *server_hostkeys, unsigned *hkflags,
     bool *can_send_ext_info, bool first_time, bool *strict_kex)
 {
@@ -1037,11 +1051,10 @@ static bool ssh2_scan_kexinits(
         clists[i] = get_string(client);
         slists[i] = get_string(server);
         if (get_err(client) || get_err(server)) {
-            /* Report a better error than the spurious "Couldn't
-             * agree" that we'd generate if we pressed on regardless
-             * and treated the empty get_string() result as genuine */
-            ssh_proto_error(ssh, "KEXINIT packet was incomplete");
-            return false;
+            ScanKexinitsResult skr = {
+                .success = false, .error = SKR_INCOMPLETE,
+            };
+            return skr;
         }
 
         for (cfirst = true, clist = clists[i];
@@ -1089,10 +1102,11 @@ static bool ssh2_scan_kexinits(
              * produce a reasonably useful message instead of an
              * assertion failure.
              */
-            ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
-                         "any supported algorithm",
-                         kexlist_descr[i], PTRLEN_PRINTF(found));
-            return false;
+            ScanKexinitsResult skr = {
+                .success = false, .error = SKR_UNKNOWN_ID,
+                .kind = kexlist_descr[i], .desc = found,
+            };
+            return skr;
         }
 
         /*
@@ -1147,9 +1161,11 @@ static bool ssh2_scan_kexinits(
             /*
              * Otherwise, any match failure _is_ a fatal error.
              */
-            ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
-                         kexlist_descr[i], PTRLEN_PRINTF(slists[i]));
-            return false;
+            ScanKexinitsResult skr = {
+                .success = false, .error = SKR_UNKNOWN_ID,
+                .kind = kexlist_descr[i], .desc = slists[i],
+            };
+            return skr;
         }
 
         switch (i) {
@@ -1245,7 +1261,33 @@ static bool ssh2_scan_kexinits(
         }
     }
 
-    return true;
+    ScanKexinitsResult skr = { .success = true };
+    return skr;
+}
+
+static void ssh2_report_scan_kexinits_error(Ssh *ssh, ScanKexinitsResult skr)
+{
+    assert(!skr.success);
+
+    switch (skr.error) {
+      case SKR_INCOMPLETE:
+        /* Report a better error than the spurious "Couldn't
+         * agree" that we'd generate if we pressed on regardless
+         * and treated the empty get_string() result as genuine */
+        ssh_proto_error(ssh, "KEXINIT packet was incomplete");
+        break;
+      case SKR_UNKNOWN_ID:
+        ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to "
+                     "any supported algorithm",
+                     skr.kind, PTRLEN_PRINTF(skr.desc));
+        break;
+      case SKR_NO_AGREEMENT:
+        ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)",
+                     skr.kind, PTRLEN_PRINTF(skr.desc));
+        break;
+      default:
+        unreachable("bad ScanKexinitsResult");
+    }
 }
 
 static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s)
@@ -1526,16 +1568,19 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
     {
         struct server_hostkeys hks = { NULL, 0, 0 };
 
-        if (!ssh2_scan_kexinits(
+        ScanKexinitsResult skr = ssh2_scan_kexinits(
                 ptrlen_from_strbuf(s->client_kexinit),
                 ptrlen_from_strbuf(s->server_kexinit), s->ssc != NULL,
                 s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans,
                 s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher,
-                &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &hks,
+                &s->warn_sccipher, NULL, &s->ignorepkt, &hks,
                 &s->hkflags, &s->can_send_ext_info, !s->got_session_id,
-                &s->strict_kex)) {
+                &s->strict_kex);
+
+        if (!skr.success) {
             sfree(hks.indices);
-            return; /* false means a fatal error function was called */
+            ssh2_report_scan_kexinits_error(s->ppl.ssh, skr);
+            return; /* we just called a fatal error function */
         }
 
         /*
