[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Implement parsing of the Match keyword from OpenSSH configuration files


Hello all,
this option was somehow left unhandled in the past and it is causing
failure in case this option is present in client configuration file.

The attached patch parses the simple options and ignores the more
complicated now. If interested, I can work also on the missing options,
but I would say they are very rarely used in the wild.

The commits are also available in gitlab:

https://gitlab.com/jjelen/libssh-mirror/commits/config-match

Regards,
-- 
Jakub Jelen
Software Engineer
Security Technologies
Red Hat, Inc.
From a8fdc335ecd91864186455be3a13c917ba1f1c11 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 10:30:09 +0200
Subject: [PATCH 1/6] config: Do not overwrite previously matched result in
 Host blocks

The match_hostname() expects comma separated list, while the Host
config keyword in openssh uses spaces separated list by default.
Therefore any subseqent match or negated match in space separated
list will overwrite the previous matches.

This also adjusts the tests to make sure both of the versions work.

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 src/config.c                      | 13 ++++++++-----
 tests/unittests/torture_options.c | 15 ++++++++++++++-
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/config.c b/src/config.c
index 6435f860..5d1baa51 100644
--- a/src/config.c
+++ b/src/config.c
@@ -406,7 +406,7 @@ static int ssh_config_parse_line(ssh_session session, const char *line,
       }
       break;
     case SOC_HOST: {
-        int ok = 0;
+        int ok = 0, result = -1;
 
         *parsing = 0;
         lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
@@ -415,14 +415,17 @@ static int ssh_config_parse_line(ssh_session session, const char *line,
              p = ssh_config_get_str_tok(&s, NULL)) {
              if (ok >= 0) {
                ok = match_hostname(lowerhost, p, strlen(p));
-               if (ok < 0) {
-                   *parsing = 0;
-               } else if (ok > 0) {
-                   *parsing = 1;
+               if (result == -1 && ok < 0) {
+                   result = 0;
+               } else if (result == -1 && ok > 0) {
+                   result = 1;
                }
             }
         }
         SAFE_FREE(lowerhost);
+        if (result != -1) {
+            *parsing = result;
+        }
         break;
     }
     case SOC_HOSTNAME:
diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c
index b531b542..be844810 100644
--- a/tests/unittests/torture_options.c
+++ b/tests/unittests/torture_options.c
@@ -359,7 +359,10 @@ static void torture_options_config_host(void **state) {
     /* create a new config file */
     config = fopen("test_config", "w");
     assert_non_null(config);
-    fputs("Host testhost1\nPort 42\nHost testhost2,testhost3\nPort 43\n", config);
+    fputs("Host testhost1\nPort 42\n"
+          "Host testhost2,testhost3\nPort 43\n"
+          "Host testhost4 testhost5\nPort 44\n",
+          config);
     fclose(config);
 
     ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1");
@@ -377,6 +380,16 @@ static void torture_options_config_host(void **state) {
     ssh_options_parse_config(session, "test_config");
     assert_int_equal(session->opts.port, 43);
 
+    ssh_options_set(session, SSH_OPTIONS_HOST, "testhost4");
+    ssh_options_parse_config(session, "test_config");
+    assert_int_equal(session->opts.port, 44);
+
+    session->opts.port = 0;
+
+    ssh_options_set(session, SSH_OPTIONS_HOST, "testhost5");
+    ssh_options_parse_config(session, "test_config");
+    assert_int_equal(session->opts.port, 44);
+
     unlink("test_config");
 }
 
-- 
2.17.1


From 86f13db108fed3a24e61c621e2f9dd4f30696075 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 13:46:37 +0200
Subject: [PATCH 2/6] config: Parse Match keyword

Amends f818e63f8, which introduced the constants and matching of this
configuration option, but did not implement the handling of the values
which was causing the configuration parser failing for certain
configurations.

This commit exposes match_pattern_list() from match.c

Red Hat Bugzilla:
https://bugzilla.redhat.com/show_bug.cgi?id=1624425

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 include/libssh/priv.h |   2 +
 src/config.c          | 163 +++++++++++++++++++++++++++++++++++++++++-
 src/match.c           |   2 +-
 3 files changed, 165 insertions(+), 2 deletions(-)

diff --git a/include/libssh/priv.h b/include/libssh/priv.h
index 2123fc03..31abb106 100644
--- a/include/libssh/priv.h
+++ b/include/libssh/priv.h
@@ -262,6 +262,8 @@ int compress_buffer(ssh_session session,ssh_buffer buf);
 int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen);
 
 /* match.c */
+int match_pattern_list(const char *string, const char *pattern,
+    unsigned int len, int dolower);
 int match_hostname(const char *host, const char *pattern, unsigned int len);
 
 /* connector.c */
diff --git a/src/config.c b/src/config.c
index 5d1baa51..71d10d66 100644
--- a/src/config.c
+++ b/src/config.c
@@ -185,6 +185,32 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
   { NULL, SOC_UNKNOWN }
 };
 
+enum ssh_config_match_e {
+    MATCH_UNKNOWN = -1,
+    MATCH_ALL,
+    MATCH_CANONICAL,
+    MATCH_EXEC,
+    MATCH_HOST,
+    MATCH_ORIGINALHOST,
+    MATCH_USER,
+    MATCH_LOCALUSER
+};
+
+struct ssh_config_match_keyword_table_s {
+    const char *name;
+    enum ssh_config_match_e opcode;
+};
+
+static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = {
+    { "all", MATCH_ALL },
+    { "canonical", MATCH_CANONICAL },
+    { "exec", MATCH_EXEC },
+    { "host", MATCH_HOST },
+    { "originalhost", MATCH_ORIGINALHOST },
+    { "user", MATCH_USER },
+    { "localuser", MATCH_LOCALUSER },
+};
+
 static int ssh_config_parse_line(ssh_session session, const char *line,
     unsigned int count, int *parsing, int seen[]);
 
@@ -351,6 +377,40 @@ static void local_parse_glob(ssh_session session,
 }
 #endif /* HAVE_GLOB */
 
+static enum ssh_config_match_e
+ssh_config_get_match_opcode(const char *keyword)
+{
+    int i;
+
+    for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) {
+        if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) {
+            return ssh_config_match_keyword_table[i].opcode;
+        }
+    }
+
+    return MATCH_UNKNOWN;
+}
+
+static int
+ssh_config_match(char *value, const char *pattern, int negate)
+{
+    int ok, result = 0;
+    char *lowervalue;
+
+    lowervalue = (value) ? ssh_lowercase(value) : NULL;
+    ok = match_pattern_list(lowervalue, pattern, strlen(pattern), 0);
+    if (ok <= 0 && negate == 1) {
+        result = 1;
+    } else if (ok > 0 && negate == 0) {
+        result = 1;
+    }
+    SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)",
+            result == 1 ? "Matched" : "Not matched", value, pattern,
+            negate == 1 ? " (negated)" : "", ok);
+    SAFE_FREE(lowervalue);
+    return result;
+}
+
 static int ssh_config_parse_line(ssh_session session, const char *line,
     unsigned int count, int *parsing, int seen[]) {
   enum ssh_config_opcode_e opcode;
@@ -384,7 +444,10 @@ static int ssh_config_parse_line(ssh_session session, const char *line,
   }
 
   opcode = ssh_config_get_opcode(keyword);
-  if (*parsing == 1 && opcode != SOC_HOST && opcode != SOC_INCLUDE &&
+  if (*parsing == 1 &&
+      opcode != SOC_HOST &&
+      opcode != SOC_MATCH &&
+      opcode != SOC_INCLUDE &&
       opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */
       if (seen[opcode] != 0) {
           SAFE_FREE(x);
@@ -405,6 +468,104 @@ static int ssh_config_parse_line(ssh_session session, const char *line,
 #endif /* HAVE_GLOB */
       }
       break;
+
+    case SOC_MATCH: {
+        int negate, result = 1, args = 0;
+        enum ssh_config_match_e opt;
+
+        *parsing = 0;
+        do {
+            p = ssh_config_get_str_tok(&s, NULL);
+            if (p == NULL || p[0] == '\0') {
+                break;
+            }
+            args++;
+            SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'",
+                    count, p);
+
+            /* If the option is prefixed with ! the result should be negated */
+            negate = 0;
+            if (p[0] == '!') {
+                negate = 1;
+                p++;
+            }
+
+            opt = ssh_config_get_match_opcode(p);
+            switch (opt) {
+            case MATCH_ALL:
+                p = ssh_config_get_str_tok(&s, NULL);
+                if (args == 1 && (p == NULL || p[0] == '\0')) {
+                    /* The first argument and end of line */
+                    if (negate == 1) {
+                        result = 0;
+                    }
+                    break;
+                }
+
+                ssh_set_error(session, SSH_FATAL,
+                              "line %d: ERROR - Match all can not be combined with "
+                              "other Match attributes", count);
+                SAFE_FREE(x);
+                return -1;
+
+            case MATCH_EXEC:
+            case MATCH_ORIGINALHOST:
+            case MATCH_LOCALUSER:
+                /* Skip one argument */
+                p = ssh_config_get_str_tok(&s, NULL);
+                args++;
+                FALL_THROUGH;
+            case MATCH_CANONICAL:
+                SSH_LOG(SSH_LOG_WARN, "line: %d: Unsupported Match keyword "
+                        "'%s', Ignoring\n", count, p);
+                result = 0;
+                break;
+
+            case MATCH_HOST:
+                /* Here we match only one argument */
+                p = ssh_config_get_str_tok(&s, NULL);
+                if (p == NULL || p[0] == '\0') {
+                    ssh_set_error(session, SSH_FATAL,
+                                  "line %d: ERROR - Match host keyword "
+                                  "requires argument", count);
+                    SAFE_FREE(x);
+                    return -1;
+                }
+                result &= ssh_config_match(session->opts.host, p, negate);
+                args++;
+                break;
+
+            case MATCH_USER:
+                /* Here we match only one argument */
+                p = ssh_config_get_str_tok(&s, NULL);
+                if (p == NULL || p[0] == '\0') {
+                    ssh_set_error(session, SSH_FATAL,
+                                  "line %d: ERROR - Match user keyword "
+                                  "requires argument", count);
+                    SAFE_FREE(x);
+                    return -1;
+                }
+                result &= ssh_config_match(session->opts.username, p, negate);
+                args++;
+                break;
+
+            case MATCH_UNKNOWN:
+            default:
+                ssh_set_error(session, SSH_FATAL,
+                              "ERROR - Unknown argument '%s' for Match keyword", p);
+                SAFE_FREE(x);
+                return -1;
+            }
+        } while (p != NULL && p[0] != '\0');
+        if (args < 1) {
+            ssh_set_error(session, SSH_FATAL,
+                          "ERROR - Match keyword requires an argument");
+            SAFE_FREE(x);
+            return -1;
+        }
+        *parsing = result;
+        break;
+    }
     case SOC_HOST: {
         int ok = 0, result = -1;
 
diff --git a/src/match.c b/src/match.c
index ab943413..06587b92 100644
--- a/src/match.c
+++ b/src/match.c
@@ -119,7 +119,7 @@ static int match_pattern(const char *s, const char *pattern) {
  * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is
  * no match at all.
  */
-static int match_pattern_list(const char *string, const char *pattern,
+int match_pattern_list(const char *string, const char *pattern,
     unsigned int len, int dolower) {
   char sub[1024];
   int negated;
-- 
2.17.1


From 2ec6b1350d82e4676f45682ef4dcd6921dc9e83b Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 13:50:51 +0200
Subject: [PATCH 3/6] tests: Missing unlink

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 tests/unittests/torture_config.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index aa4bfd2d..7eb510b0 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -42,6 +42,7 @@ static int setup_config_files(void **state)
     unlink(LIBSSH_TESTCONFIG6);
     unlink(LIBSSH_TESTCONFIG7);
     unlink(LIBSSH_TESTCONFIG8);
+    unlink(LIBSSH_TESTCONFIG9);
 
     torture_write_file(LIBSSH_TESTCONFIG1,
                        "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n");
-- 
2.17.1


From 75f4efc4054d1854f1612287011d829ce2211728 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 15:29:01 +0200
Subject: [PATCH 4/6] tests: Use global verbosity in tests

This allows adjusting the log level of config and options tests using
environment variable LIBSSH_VERBOSITY as it works in most of the other
tests.

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 tests/unittests/torture_config.c  | 5 +++++
 tests/unittests/torture_options.c | 9 ++++++++-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index 7eb510b0..b1fdb51e 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -33,6 +33,7 @@ extern LIBSSH_THREAD int ssh_log_level;
 static int setup_config_files(void **state)
 {
     ssh_session session;
+    int verbosity;
 
     unlink(LIBSSH_TESTCONFIG1);
     unlink(LIBSSH_TESTCONFIG2);
@@ -106,6 +107,10 @@ static int setup_config_files(void **state)
                         "");
 
     session = ssh_new();
+
+    verbosity = torture_libssh_verbosity();
+    ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+
     *state = session;
 
     return 0;
diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c
index be844810..06a12e81 100644
--- a/tests/unittests/torture_options.c
+++ b/tests/unittests/torture_options.c
@@ -15,7 +15,14 @@
 
 static int setup(void **state)
 {
-    ssh_session session = ssh_new();
+    ssh_session session;
+    int verbosity;
+
+    session = ssh_new();
+
+    verbosity = torture_libssh_verbosity();
+    ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+
     *state = session;
 
     return 0;
-- 
2.17.1


From 95066baef224f2b37d182815fe6dd33cd493d8c8 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 15:35:05 +0200
Subject: [PATCH 5/6] tests: No need to restore log level now

Since the verbosity is now set from the setup phase, we do not
need to reset the verbosity, especially not to any arbirary value
such as WARNING.

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 tests/unittests/torture_config.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index b1fdb51e..4b225fdd 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -220,7 +220,6 @@ static void torture_config_new(void **state)
 {
     ssh_session session = *state;
     int ret = 0;
-    int verbosity = SSH_LOG_WARNING;
 
     ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG7);
     assert_true(ret == 0);
@@ -232,9 +231,6 @@ static void torture_config_new(void **state)
 
     assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE);
     assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE);
-
-    /* reset to something sane */
-    ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
 }
 
 /**
-- 
2.17.1


From 2e7e34f77a353e724b4d4cb5cff167d866e272b0 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@xxxxxxxxxx>
Date: Tue, 4 Sep 2018 19:09:08 +0200
Subject: [PATCH 6/6] tests: Verify the Match keyword from configuration file

Signed-off-by: Jakub Jelen <jjelen@xxxxxxxxxx>
---
 tests/unittests/torture_config.c  |  75 +++++++++++++++++
 tests/unittests/torture_options.c | 131 +++++++++++++++++++++++++++++-
 2 files changed, 205 insertions(+), 1 deletion(-)

diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index 4b225fdd..7c00daa2 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -17,6 +17,7 @@ extern LIBSSH_THREAD int ssh_log_level;
 #define LIBSSH_TESTCONFIG7 "libssh_testconfig7.tmp"
 #define LIBSSH_TESTCONFIG8 "libssh_testconfig8.tmp"
 #define LIBSSH_TESTCONFIG9 "libssh_testconfig9.tmp"
+#define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp"
 #define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp"
 
 #define USERNAME "testuser"
@@ -106,6 +107,22 @@ static int setup_config_files(void **state)
                         "VisualHostkey yes\n" /* SOC_UNSUPPORTED */
                         "");
 
+    /* Match keyword */
+    torture_write_file(LIBSSH_TESTCONFIG10,
+                       "Match host example\n"
+                       "\tHostName example.com\n"
+                       "Match host example1,example2\n"
+                       "\tHostName exampleN\n"
+                       "Match user guest\n"
+                       "\tHostName guest.com\n"
+                       "Match user tester host testhost\n"
+                       "\tHostName testhost.com\n"
+                       "Match !user tester host testhost\n"
+                       "\tHostName nonuser-testhost.com\n"
+                       "Match all\n"
+                       "\tHostName all-matched.com\n"
+                       "");
+
     session = ssh_new();
 
     verbosity = torture_libssh_verbosity();
@@ -127,6 +144,7 @@ static int teardown(void **state)
     unlink(LIBSSH_TESTCONFIG7);
     unlink(LIBSSH_TESTCONFIG8);
     unlink(LIBSSH_TESTCONFIG9);
+    unlink(LIBSSH_TESTCONFIG10);
 
     ssh_free(*state);
 
@@ -303,6 +321,60 @@ static void torture_config_unknown(void **state) {
     assert_true(ret == 0);
 }
 
+
+/**
+ * @brief Verify the configuration parser accepts Match keyword with
+ * full OpenSSH syntax.
+ */
+static void torture_config_match(void **state) {
+    ssh_session session = *state;
+    int ret = 0;
+
+    /* Without any settings we should get all-matched.com hostname */
+    ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "all-matched.com");
+
+    /* Hostname example does simple hostname matching */
+    ssh_options_set(session, SSH_OPTIONS_HOST, "example");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "example.com");
+
+    /* We can match also both hosts from a comma separated list */
+    ssh_options_set(session, SSH_OPTIONS_HOST, "example1");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "exampleN");
+
+    ssh_options_set(session, SSH_OPTIONS_HOST, "example2");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "exampleN");
+
+    /* We can match by user */
+    ssh_options_set(session, SSH_OPTIONS_USER, "guest");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "guest.com");
+
+    /* We can combine two options on a single line to match both of them */
+    ssh_options_set(session, SSH_OPTIONS_USER, "tester");
+    ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "testhost.com");
+
+    /* We can also negate conditions */
+    ssh_options_set(session, SSH_OPTIONS_USER, "not-tester");
+    ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
+    ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10);
+    assert_true(ret == 0);
+    assert_string_equal(session->opts.host, "nonuser-testhost.com");
+
+}
+
 int torture_run_tests(void) {
     int rc;
     struct CMUnitTest tests[] = {
@@ -324,6 +396,9 @@ int torture_run_tests(void) {
         cmocka_unit_test_setup_teardown(torture_config_unknown,
                                         setup_config_files,
                                         teardown),
+        cmocka_unit_test_setup_teardown(torture_config_match,
+                                        setup_config_files,
+                                        teardown),
     };
 
 
diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c
index 06a12e81..bc52b900 100644
--- a/tests/unittests/torture_options.c
+++ b/tests/unittests/torture_options.c
@@ -400,6 +400,133 @@ static void torture_options_config_host(void **state) {
     unlink("test_config");
 }
 
+static void torture_options_config_match(void **state)
+{
+    ssh_session session = *state;
+    FILE *config = NULL;
+    int rv;
+
+    /* Required for options_parse_config() */
+    ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1");
+
+    /* The Match keyword requires argument */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_ERROR);
+
+    /* The Match all keyword needs to be the only one (start) */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match all host local\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_ERROR);
+
+    /* The Match all keyword needs to be the only one (end) */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match host local all\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_ERROR);
+
+    /* The Match host keyword requires an argument */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match host\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_ERROR);
+
+    /* The Match user keyword requires an argument */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match user\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_ERROR);
+
+    /* The Match canonical keyword is ignored */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match canonical\n"
+          "\tPort 33\n"
+          "Match all\n"
+          "\tPort 34\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_OK);
+    assert_int_equal(session->opts.port, 34);
+
+    session->opts.port = 0;
+
+    /* The Match originalhost keyword is ignored */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match originalhost origin\n"
+          "\tPort 33\n"
+          "Match all\n"
+          "\tPort 34\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_OK);
+    assert_int_equal(session->opts.port, 34);
+
+    session->opts.port = 0;
+
+    /* The Match localuser keyword is ignored */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match originalhost origin\n"
+          "\tPort 33\n"
+          "Match all\n"
+          "\tPort 34\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_OK);
+    assert_int_equal(session->opts.port, 34);
+
+    session->opts.port = 0;
+
+    /* The Match exec keyword is ignored */
+    config = fopen("test_config", "w");
+    assert_non_null(config);
+    fputs("Match exec /bin/true\n"
+          "\tPort 33\n"
+          "Match all\n"
+          "\tPort 34\n",
+          config);
+    fclose(config);
+
+    rv = ssh_options_parse_config(session, "test_config");
+    assert_int_equal(rv, SSH_OK);
+    assert_int_equal(session->opts.port, 34);
+
+    session->opts.port = 0;
+
+    unlink("test_config");
+}
+
+
 
 #ifdef WITH_SERVER
 /* sshbind options */
@@ -472,7 +599,9 @@ int torture_run_tests(void) {
         cmocka_unit_test_setup_teardown(torture_options_set_hostkey, setup, teardown),
         cmocka_unit_test_setup_teardown(torture_options_set_pubkey_accepted_types, setup, teardown),
         cmocka_unit_test_setup_teardown(torture_options_set_macs, setup, teardown),
-        cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown)
+        cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown),
+        cmocka_unit_test_setup_teardown(torture_options_config_match,
+                                        setup, teardown)
     };
 
 #ifdef WITH_SERVER
-- 
2.17.1


Archive administrator: postmaster@lists.cynapses.org