69d2dd9
From 4f997aee7c7d7ea346b3e8ba505da0b7601ff318 Mon Sep 17 00:00:00 2001
69d2dd9
From: Jakub Jelen <jjelen@redhat.com>
69d2dd9
Date: Fri, 22 Dec 2023 10:32:40 +0100
69d2dd9
Subject: [PATCH 1/2] Fix regression in IPv6 addresses in hostname parsing
69d2dd9
69d2dd9
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
69d2dd9
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
69d2dd9
---
69d2dd9
 include/libssh/config_parser.h | 11 ++++++++---
69d2dd9
 src/config.c                   |  4 ++--
69d2dd9
 src/config_parser.c            | 16 +++++++++++-----
69d2dd9
 src/options.c                  | 10 ++--------
69d2dd9
 4 files changed, 23 insertions(+), 18 deletions(-)
69d2dd9
69d2dd9
diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h
69d2dd9
index a7dd42a2..ca353432 100644
69d2dd9
--- a/include/libssh/config_parser.h
69d2dd9
+++ b/include/libssh/config_parser.h
69d2dd9
@@ -30,6 +30,8 @@
69d2dd9
 extern "C" {
69d2dd9
 #endif
69d2dd9
 
69d2dd9
+#include <stdbool.h>
69d2dd9
+
69d2dd9
 char *ssh_config_get_cmd(char **str);
69d2dd9
 
69d2dd9
 char *ssh_config_get_token(char **str);
69d2dd9
@@ -49,14 +51,17 @@ int ssh_config_get_yesno(char **str, int notfound);
69d2dd9
  *                       be stored or NULL if we do not care about the result.
69d2dd9
  * @param[out]  port     Pointer to the location, where the new port will
69d2dd9
  *                       be stored or NULL if we do not care about the result.
69d2dd9
+ * @param[in]   ignore_port Set to true if the we should not attempt to parse
69d2dd9
+ *                       port number.
69d2dd9
  *
69d2dd9
  * @returns     SSH_OK if the provided string is in format of SSH URI,
69d2dd9
  *              SSH_ERROR on failure
69d2dd9
  */
69d2dd9
 int ssh_config_parse_uri(const char *tok,
69d2dd9
-        char **username,
69d2dd9
-        char **hostname,
69d2dd9
-        char **port);
69d2dd9
+                         char **username,
69d2dd9
+                         char **hostname,
69d2dd9
+                         char **port,
69d2dd9
+                         bool ignore_port);
69d2dd9
 
69d2dd9
 #ifdef __cplusplus
69d2dd9
 }
69d2dd9
diff --git a/src/config.c b/src/config.c
69d2dd9
index 5eedbce9..7135c3b1 100644
69d2dd9
--- a/src/config.c
69d2dd9
+++ b/src/config.c
69d2dd9
@@ -464,7 +464,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
69d2dd9
         }
69d2dd9
         if (parse_entry) {
69d2dd9
             /* We actually care only about the first item */
69d2dd9
-            rv = ssh_config_parse_uri(cp, &username, &hostname, &port);
69d2dd9
+            rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false);
69d2dd9
             /* The rest of the list needs to be passed on */
69d2dd9
             if (endp != NULL) {
69d2dd9
                 next = strdup(endp + 1);
69d2dd9
@@ -475,7 +475,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
69d2dd9
             }
69d2dd9
         } else {
69d2dd9
             /* The rest is just sanity-checked to avoid failures later */
69d2dd9
-            rv = ssh_config_parse_uri(cp, NULL, NULL, NULL);
69d2dd9
+            rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
69d2dd9
         }
69d2dd9
         if (rv != SSH_OK) {
69d2dd9
             goto out;
69d2dd9
diff --git a/src/config_parser.c b/src/config_parser.c
69d2dd9
index 9ffc8b8b..5f30cd3e 100644
69d2dd9
--- a/src/config_parser.c
69d2dd9
+++ b/src/config_parser.c
69d2dd9
@@ -162,9 +162,10 @@ int ssh_config_get_yesno(char **str, int notfound)
69d2dd9
 }
69d2dd9
 
69d2dd9
 int ssh_config_parse_uri(const char *tok,
69d2dd9
-        char **username,
69d2dd9
-        char **hostname,
69d2dd9
-        char **port)
69d2dd9
+                         char **username,
69d2dd9
+                         char **hostname,
69d2dd9
+                         char **port,
69d2dd9
+                         bool ignore_port)
69d2dd9
 {
69d2dd9
     char *endp = NULL;
69d2dd9
     long port_n;
69d2dd9
@@ -210,12 +211,17 @@ int ssh_config_parse_uri(const char *tok,
69d2dd9
         if (endp == NULL) {
69d2dd9
             goto error;
69d2dd9
         }
69d2dd9
-    } else {
69d2dd9
-        /* Hostnames or aliases expand to the last colon or to the end */
69d2dd9
+    } else if (!ignore_port) {
69d2dd9
+        /* Hostnames or aliases expand to the last colon (if port is requested)
69d2dd9
+         * or to the end */
69d2dd9
         endp = strrchr(tok, ':');
69d2dd9
         if (endp == NULL) {
69d2dd9
             endp = strchr(tok, '\0');
69d2dd9
         }
69d2dd9
+    } else {
69d2dd9
+        /* If no port is requested, expand to the end of line
69d2dd9
+         * (to accommodate the IPv6 addresses) */
69d2dd9
+        endp = strchr(tok, '\0');
69d2dd9
     }
69d2dd9
     if (tok == endp) {
69d2dd9
         /* Zero-length hostnames are not valid */
69d2dd9
diff --git a/src/options.c b/src/options.c
69d2dd9
index 2e73be46..676c49e7 100644
69d2dd9
--- a/src/options.c
69d2dd9
+++ b/src/options.c
69d2dd9
@@ -634,17 +634,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
69d2dd9
                 ssh_set_error_invalid(session);
69d2dd9
                 return -1;
69d2dd9
             } else {
69d2dd9
-                char *username = NULL, *hostname = NULL, *port = NULL;
69d2dd9
-                rc = ssh_config_parse_uri(value, &username, &hostname, &port);
69d2dd9
+                char *username = NULL, *hostname = NULL;
69d2dd9
+                rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true);
69d2dd9
                 if (rc != SSH_OK) {
69d2dd9
                     return -1;
69d2dd9
                 }
69d2dd9
-                if (port != NULL) {
69d2dd9
-                    SAFE_FREE(username);
69d2dd9
-                    SAFE_FREE(hostname);
69d2dd9
-                    SAFE_FREE(port);
69d2dd9
-                    return -1;
69d2dd9
-                }
69d2dd9
                 if (username != NULL) {
69d2dd9
                     SAFE_FREE(session->opts.username);
69d2dd9
                     session->opts.username = username;
69d2dd9
-- 
69d2dd9
2.43.0
69d2dd9
69d2dd9
69d2dd9
From 6f6e453d7b0ad4ee6a6f6a1c96a9a6b27821410d Mon Sep 17 00:00:00 2001
69d2dd9
From: Jakub Jelen <jjelen@redhat.com>
69d2dd9
Date: Fri, 22 Dec 2023 09:52:18 +0100
69d2dd9
Subject: [PATCH 2/2] tests: Increase test coverage for IPv6 address parsing as
69d2dd9
 hostnames
69d2dd9
69d2dd9
This was an issue in cockpit:
69d2dd9
69d2dd9
https://github.com/cockpit-project/cockpit/issues/19772
69d2dd9
69d2dd9
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
69d2dd9
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
69d2dd9
---
69d2dd9
 tests/unittests/torture_config.c  | 49 +++++++++++++++++++++++++++++++
69d2dd9
 tests/unittests/torture_options.c | 16 ++++++++++
69d2dd9
 2 files changed, 65 insertions(+)
69d2dd9
69d2dd9
diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
69d2dd9
index bc6b08f9..751aa126 100644
69d2dd9
--- a/tests/unittests/torture_config.c
69d2dd9
+++ b/tests/unittests/torture_config.c
69d2dd9
@@ -2332,6 +2332,53 @@ static void torture_config_make_absolute_no_sshdir(void **state)
69d2dd9
     torture_config_make_absolute_int(state, 1);
69d2dd9
 }
69d2dd9
 
69d2dd9
+static void torture_config_parse_uri(void **state)
69d2dd9
+{
69d2dd9
+    char *username = NULL;
69d2dd9
+    char *hostname = NULL;
69d2dd9
+    char *port = NULL;
69d2dd9
+    int rc;
69d2dd9
+
69d2dd9
+    (void)state; /* unused */
69d2dd9
+
69d2dd9
+    rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false);
69d2dd9
+    assert_return_code(rc, errno);
69d2dd9
+    assert_null(username);
69d2dd9
+    assert_string_equal(hostname, "localhost");
69d2dd9
+    SAFE_FREE(hostname);
69d2dd9
+    assert_null(port);
69d2dd9
+
69d2dd9
+    rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false);
69d2dd9
+    assert_return_code(rc, errno);
69d2dd9
+    assert_null(username);
69d2dd9
+    assert_string_equal(hostname, "1.2.3.4");
69d2dd9
+    SAFE_FREE(hostname);
69d2dd9
+    assert_null(port);
69d2dd9
+
69d2dd9
+    rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false);
69d2dd9
+    assert_return_code(rc, errno);
69d2dd9
+    assert_null(username);
69d2dd9
+    assert_string_equal(hostname, "1.2.3.4");
69d2dd9
+    SAFE_FREE(hostname);
69d2dd9
+    assert_string_equal(port, "2222");
69d2dd9
+    SAFE_FREE(port);
69d2dd9
+
69d2dd9
+    rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false);
69d2dd9
+    assert_return_code(rc, errno);
69d2dd9
+    assert_null(username);
69d2dd9
+    assert_string_equal(hostname, "1:2:3::4");
69d2dd9
+    SAFE_FREE(hostname);
69d2dd9
+    assert_string_equal(port, "2222");
69d2dd9
+    SAFE_FREE(port);
69d2dd9
+
69d2dd9
+    /* do not want port */
69d2dd9
+    rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true);
69d2dd9
+    assert_return_code(rc, errno);
69d2dd9
+    assert_null(username);
69d2dd9
+    assert_string_equal(hostname, "1:2:3::4");
69d2dd9
+    SAFE_FREE(hostname);
69d2dd9
+}
69d2dd9
+
69d2dd9
 int torture_run_tests(void)
69d2dd9
 {
69d2dd9
     int rc;
69d2dd9
@@ -2424,6 +2471,8 @@ int torture_run_tests(void)
69d2dd9
                                         setup, teardown),
69d2dd9
         cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir,
69d2dd9
                                         setup_no_sshdir, teardown),
69d2dd9
+        cmocka_unit_test_setup_teardown(torture_config_parse_uri,
69d2dd9
+                                        setup, teardown),
69d2dd9
     };
69d2dd9
 
69d2dd9
 
69d2dd9
diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c
69d2dd9
index 5ba3bdc6..b07712d8 100644
69d2dd9
--- a/tests/unittests/torture_options.c
69d2dd9
+++ b/tests/unittests/torture_options.c
69d2dd9
@@ -57,6 +57,20 @@ static void torture_options_set_host(void **state) {
69d2dd9
     assert_non_null(session->opts.host);
69d2dd9
     assert_string_equal(session->opts.host, "localhost");
69d2dd9
 
69d2dd9
+    /* IPv4 address */
69d2dd9
+    rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1");
69d2dd9
+    assert_true(rc == 0);
69d2dd9
+    assert_non_null(session->opts.host);
69d2dd9
+    assert_string_equal(session->opts.host, "127.1.1.1");
69d2dd9
+    assert_null(session->opts.username);
69d2dd9
+
69d2dd9
+    /* IPv6 address */
69d2dd9
+    rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1");
69d2dd9
+    assert_true(rc == 0);
69d2dd9
+    assert_non_null(session->opts.host);
69d2dd9
+    assert_string_equal(session->opts.host, "::1");
69d2dd9
+    assert_null(session->opts.username);
69d2dd9
+
69d2dd9
     rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation");
69d2dd9
     assert_true(rc == 0);
69d2dd9
     assert_non_null(session->opts.host);
69d2dd9
@@ -64,12 +78,14 @@ static void torture_options_set_host(void **state) {
69d2dd9
     assert_non_null(session->opts.username);
69d2dd9
     assert_string_equal(session->opts.username, "guru");
69d2dd9
 
69d2dd9
+    /* more @ in uri is OK -- it should go to the username */
69d2dd9
     rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname");
69d2dd9
     assert_true(rc == 0);
69d2dd9
     assert_non_null(session->opts.host);
69d2dd9
     assert_string_equal(session->opts.host, "hostname");
69d2dd9
     assert_non_null(session->opts.username);
69d2dd9
     assert_string_equal(session->opts.username, "at@login");
69d2dd9
+
69d2dd9
 }
69d2dd9
 
69d2dd9
 static void torture_options_set_ciphers(void **state) {
69d2dd9
-- 
69d2dd9
2.43.0
69d2dd9