diff options
Diffstat (limited to 'tests/unittests/torture_config.c')
-rw-r--r-- | tests/unittests/torture_config.c | 876 |
1 files changed, 827 insertions, 49 deletions
diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 08b67deb..ebc2cdbd 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -2,11 +2,18 @@ #define LIBSSH_STATIC +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include <pwd.h> +#endif + #include "torture.h" #include "libssh/options.h" #include "libssh/session.h" #include "libssh/config_parser.h" #include "match.c" +#include "config.c" +#include "libssh/socket.h" extern LIBSSH_THREAD int ssh_log_level; @@ -34,10 +41,16 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" #define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" #define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIG14 "libssh_testconfig14.tmp" +#define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" +#define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" +#define LIBSSH_TESTCONFIG17 "libssh_testconfig17.tmp" #define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" #define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" #define LIBSSH_TEST_NONEWLINEEND "libssh_test_NoNewLineEnd.tmp" #define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" +#define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" #define LIBSSH_TESTCONFIG_STRING1 \ "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" @@ -50,7 +63,7 @@ extern LIBSSH_THREAD int ssh_log_level; "\n\nIdentityFile "ID_FILE"\n" \ "\n\nKexAlgorithms "KEXALGORITHMS"\n" \ "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ - "\n\nPubkeyAcceptedTypes "PUBKEYACCEPTEDTYPES"\n" \ + "\n\nPubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" \ "\n\nMACs "MACS"\n" /* Multiple Port settings -> parsing returns early. */ @@ -168,14 +181,49 @@ extern LIBSSH_THREAD int ssh_log_level; "Host time4\n" \ "\tRekeyLimit default 9600\n" -/* Multiple IdentityFile settings all are aplied */ +/* Multiple IdentityFile settings all are applied */ #define LIBSSH_TESTCONFIG_STRING13 \ "IdentityFile id_rsa_one\n" \ - "IdentityFile id_ecdsa_two\n" + "CertificateFile id_rsa_one-cert.pub\n" \ + "IdentityFile id_ecdsa_two\n" \ + "CertificateFile id_ecdsa_two-cert.pub\n" \ + +/* +,-,^ features for all supported list */ +/* kex won't work in fips */ +#define LIBSSH_TESTCONFIG_STRING14 \ + "HostKeyAlgorithms +ssh-rsa\n" \ + "Ciphers +aes128-cbc,aes256-cbc\n" \ + "KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group1-sha1\n" \ + "MACs +hmac-sha1,hmac-sha1-etm@openssh.com\n" + +/* have to be algorithms which are in the default list */ +#define LIBSSH_TESTCONFIG_STRING15 \ + "HostKeyAlgorithms -rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers -aes256-ctr\n" \ + "KexAlgorithms -diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs -hmac-sha2-256-etm@openssh.com\n" + +#define LIBSSH_TESTCONFIG_STRING16 \ + "HostKeyAlgorithms ^rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers ^aes256-cbc\n" \ + "KexAlgorithms ^diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs ^hmac-sha1\n" + +/* Connection Multiplexing */ +#define LIBSSH_TESTCONFIG_STRING17 \ + "Host simple\n" \ + "\tControlMaster auto\n" \ + "\tControlPath /tmp/ssh-%r@%h:%p\n" \ + "Host none\n" \ + "\tControlMaster yes\n" \ + "\tControlPath none\n" #define LIBSSH_TEST_PUBKEYTYPES_STRING \ "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" +#define LIBSSH_TEST_PUBKEYALGORITHMS_STRING \ + "PubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" + #define LIBSSH_TEST_NONEWLINEEND_STRING \ "ConnectTimeout 30\n" \ "LogLevel DEBUG3" @@ -183,6 +231,9 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TEST_NONEWLINEONELINE_STRING \ "ConnectTimeout 30" +#define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ + "Include " LIBSSH_TEST_RECURSIVE_INCLUDE + /** * @brief helper function loading configuration from either file or string */ @@ -224,7 +275,12 @@ static int setup_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); unlink(LIBSSH_TEST_NONEWLINEEND); unlink(LIBSSH_TEST_NONEWLINEONELINE); @@ -270,9 +326,22 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TESTCONFIG12, LIBSSH_TESTCONFIG_STRING12); + /* +,-,^ feature */ + torture_write_file(LIBSSH_TESTCONFIG14, + LIBSSH_TESTCONFIG_STRING14); + torture_write_file(LIBSSH_TESTCONFIG15, + LIBSSH_TESTCONFIG_STRING15); + torture_write_file(LIBSSH_TESTCONFIG16, + LIBSSH_TESTCONFIG_STRING16); + torture_write_file(LIBSSH_TESTCONFIG17, + LIBSSH_TESTCONFIG_STRING17); + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, LIBSSH_TEST_PUBKEYTYPES_STRING); + torture_write_file(LIBSSH_TEST_PUBKEYALGORITHMS, + LIBSSH_TEST_PUBKEYALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_NONEWLINEEND, LIBSSH_TEST_NONEWLINEEND_STRING); @@ -298,7 +367,12 @@ static int teardown_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); return 0; } @@ -306,6 +380,25 @@ static int teardown_config_files(void **state) static int setup(void **state) { ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int setup_no_sshdir(void **state) +{ + ssh_session session = NULL; int verbosity; session = ssh_new(); @@ -418,6 +511,22 @@ static void torture_config_include_string(void **state) } /** + * @brief tests ssh_config_parse_file with recursive Include directives from file + */ +static void torture_config_include_recursive_file(void **state) +{ + _parse_config(*state, LIBSSH_TEST_RECURSIVE_INCLUDE, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_recursive_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TEST_RECURSIVE_INCLUDE_STRING, SSH_OK); +} + +/** * @brief tests ssh_config_parse_file with multiple Port settings. */ static void torture_config_double_ports_file(void **state) @@ -487,12 +596,14 @@ static void torture_config_new(void ** state, assert_string_equal(session->opts.bindaddr, BIND_ADDRESS); #ifdef WITH_ZLIB assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,none"); #else - assert_null(session->opts.wanted_methods[SSH_COMP_C_S]); - assert_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); #endif /* WITH_ZLIB */ assert_int_equal(session->opts.StrictHostKeyChecking, 0); assert_int_equal(session->opts.gss_delegate_creds, 1); @@ -593,7 +704,7 @@ static void torture_config_unknown(void **state, /* test corner cases */ _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p many-spaces.com"); + "ssh -W '[%h]:%p' many-spaces.com"); assert_string_equal(session->opts.host, "equal.sign"); ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); @@ -889,28 +1000,28 @@ static void torture_config_proxyjump(void **state, torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); _parse_config(session, file, string, SSH_OK); - assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); + assert_string_equal(session->opts.ProxyCommand, "ssh -W '[%h]:%p' jumpbox"); /* With username */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "user"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -l user -W [%h]:%p jumpbox"); + "ssh -l user -W '[%h]:%p' jumpbox"); /* With port */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "port"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -p 2222 -W [%h]:%p jumpbox"); + "ssh -p 2222 -W '[%h]:%p' jumpbox"); /* Two step jump */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -l u1 -p 222 -J u2@second:33 -W [%h]:%p first"); + "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); /* none */ torture_reset_config(session); @@ -918,43 +1029,42 @@ static void torture_config_proxyjump(void **state, _parse_config(session, file, string, SSH_OK); assert_true(session->opts.ProxyCommand == NULL); - /* If also ProxyCommand is specifed, the first is applied */ + /* If also ProxyCommand is specified, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, PROXYCMD); - /* If also ProxyCommand is specifed, the first is applied */ + /* If also ProxyCommand is specified, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p jumpbox"); + "ssh -W '[%h]:%p' jumpbox"); /* IPv6 address */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, - "ssh -W [%h]:%p 2620:52:0::fed"); + "ssh -W '[%h]:%p' 2620:52:0::fed"); - /* In this part, we try various other config files and strings. */ - - /* Try to create some invalid configurations */ - /* Non-numeric port */ - config = "Host bad-port\n" - "\tProxyJump jumpbox:22bad22\n"; + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); } else { string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); - _parse_config(session, file, string, SSH_ERROR); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); - /* Too many @ */ - config = "Host bad-hostname\n" + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" "\tProxyJump user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); @@ -962,7 +1072,24 @@ static void torture_config_proxyjump(void **state, string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user@principal.com -p 22 -W '[%h]:%p' jumpbox"); + + /* In this part, we try various other config files and strings. */ + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + config = "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); _parse_config(session, file, string, SSH_ERROR); /* Braces mismatch in hostname */ @@ -1037,18 +1164,6 @@ static void torture_config_proxyjump(void **state, ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); _parse_config(session, file, string, SSH_ERROR); - /* Too many @ in second jump */ - config = "Host bad-hostname\n" - "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; - if (file != NULL) { - torture_write_file(file, config); - } else { - string = config; - } - torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); - _parse_config(session, file, string, SSH_ERROR); - /* Braces mismatch in second jump */ config = "Host mismatch\n" "\tProxyJump localhost,[::1:20\n"; @@ -1127,6 +1242,76 @@ static void torture_config_proxyjump_string(void **state) } /** + * @brief Verify we can parse ControlPath configuration option + */ +static void torture_config_control_path(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); +} + +/** + * @brief Verify we can parse ControlPath configuration option from string + */ +static void torture_config_control_path_string(void **state) +{ + torture_config_control_path(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlPath configuration option from file + */ +static void torture_config_control_path_file(void **state) +{ + torture_config_control_path(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse ControlMaster configuration option + */ +static void torture_config_control_master(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTO); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_YES); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from string + */ +static void torture_config_control_master_string(void **state) +{ + torture_config_control_master(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from file + */ +static void torture_config_control_master_file(void **state) +{ + torture_config_control_master(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** * @brief Verify the configuration parser handles all the possible * versions of RekeyLimit configuration option. */ @@ -1212,6 +1397,349 @@ static void torture_config_rekey_string(void **state) } /** + * @brief Remove substring from a string + * + * @param occurrence 0 means "remove the first occurrence" + * 1 means "remove the second occurrence" and so on + */ +static void helper_remove_substring(char *s, const char *subs, int occurrence) { + char *p; + /* remove the substring from the defaults */ + p = strstr(s, subs); + assert_non_null(p); + /* look for second occurrence */ + for (int i = 0; i < occurrence; i++) { + p = strstr(p + 1, subs); + assert_non_null(p); + } + memmove(p, p + strlen(subs), strlen(p + strlen(subs)) + 1); +} + +/** + * @brief test that openssh style '+' feature works + */ +static void torture_config_plus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_added = ",ssh-rsa"; + const char *ciphers_added = "aes128-cbc,aes256-cbc"; + const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + /* ssh-rsa is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_added) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + strlen(hostkeys_added) + 1, + "%s%s", def_hostkeys, hostkeys_added); + assert_int_equal(rc, strlen(def_hostkeys) + strlen(hostkeys_added)); + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + } + + /* check ciphers */ + if (ssh_fips_mode()) { + /* already all supported is in the list */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + awaited = calloc(strlen(def_ciphers) + strlen(ciphers_added) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + strlen(ciphers_added) + 1, + "%s%s", def_ciphers, ciphers_added); + assert_int_equal(rc, strlen(def_ciphers) + strlen(ciphers_added)); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + } + + /* check kex */ + if (ssh_fips_mode()) { + /* sha1 is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_kex); + } else { + awaited = calloc(strlen(def_kex) + strlen(kex_added) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + strlen(kex_added) + 1, + "%s%s", def_kex, kex_added); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_added)); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + } + + /* check mac */ + if (ssh_fips_mode()) { + /* the added algos are already in the fips_methods */ + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_mac); + } else { + awaited = calloc(strlen(def_mac) + strlen(mac_added) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + strlen(mac_added) + 1, + "%s%s", def_mac, mac_added); + assert_int_equal(rc, strlen(def_mac) + strlen(mac_added)); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); + } +} + +/** + * @brief test that openssh style '+' feature works from file + */ +static void torture_config_plus_file(void **state) +{ + torture_config_plus(state, LIBSSH_TESTCONFIG14, NULL); +} + +/** + * @brief test that openssh style '+' feature works from string + */ +static void torture_config_plus_string(void **state) +{ + torture_config_plus(state, NULL, LIBSSH_TESTCONFIG_STRING14); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_removed = ",rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_removed = ",aes256-ctr"; + const char *kex_removed = ",diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + const char *fips_kex_removed = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_removed = "hmac-sha2-256-etm@openssh.com,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(fips_hostkeys) + 1, "%s", fips_hostkeys); + assert_int_equal(rc, strlen(fips_hostkeys)); + } else { + awaited = calloc(strlen(def_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + 1, "%s", def_hostkeys); + assert_int_equal(rc, strlen(def_hostkeys)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(fips_ciphers) + 1, "%s", fips_ciphers); + assert_int_equal(rc, strlen(fips_ciphers)); + } else { + awaited = calloc(strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); + assert_int_equal(rc, strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(fips_kex) + 1, "%s", fips_kex); + assert_int_equal(rc, strlen(fips_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, fips_kex_removed, 0); + } else { + awaited = calloc(strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + 1, "%s", def_kex); + assert_int_equal(rc, strlen(def_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_removed, 0); + } + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(fips_mac) + 1, "%s", fips_mac); + assert_int_equal(rc, strlen(fips_mac)); + } else { + awaited = calloc(strlen(def_mac) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + 1, "%s", def_mac); + assert_int_equal(rc, strlen(def_mac)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, mac_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '-' feature works from file + */ +static void torture_config_minus_file(void **state) +{ + torture_config_minus(state, LIBSSH_TESTCONFIG15, NULL); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus_string(void **state) +{ + torture_config_minus(state, NULL, LIBSSH_TESTCONFIG_STRING15); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_prio = "rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_prio = "aes256-cbc,"; + const char *kex_prio = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,"; + const char *fips_kex_prio = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_prio = "hmac-sha1,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + /* +2 for the added comma and the \0 */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, + "%s,%s", hostkeys_prio, fips_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 1); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_prio) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(def_hostkeys) + 2, + "%s,%s", hostkeys_prio, def_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(def_hostkeys) + 1); + } + + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_prio, 1); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(ciphers_prio) + strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(fips_ciphers) + 1, + "%s%s", ciphers_prio, fips_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(fips_ciphers)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_prio, 1); + } else { + /* + 2 because the '\0' and the comma */ + awaited = calloc(strlen(ciphers_prio) + strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, + "%s%s", ciphers_prio, def_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + } + + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(kex_prio) + strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(fips_kex) + 1, + "%s%s", kex_prio, fips_kex); + assert_int_equal(rc, strlen(kex_prio) + strlen(fips_kex)); + /* remove the substring from the defaults */ + /* the default list has different order of these two algos than the fips + * and because here is a braindead string substitution being done, + * change the order and remove the first occurrence of it */ + helper_remove_substring(awaited, fips_kex_prio, 0); + } else { + awaited = calloc(strlen(kex_prio) + strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(def_kex) + 1, + "%s%s", kex_prio, def_kex); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_prio)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_prio, 1); + } + + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(mac_prio) + strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(mac_prio) + strlen(fips_mac) + 1, "%s%s", mac_prio, fips_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(fips_mac)); + /* the fips list contains hmac-sha1 algo */ + helper_remove_substring(awaited, mac_prio, 1); + } else { + awaited = calloc(strlen(mac_prio) + strlen(def_mac) + 1, 1); + /* the mac is not in default; it is added to the list */ + rc = snprintf(awaited, strlen(mac_prio) + strlen(def_mac) + 1, "%s%s", mac_prio, def_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(def_mac)); + } + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '^' feature works from file + */ +static void torture_config_caret_file(void **state) +{ + torture_config_caret(state, LIBSSH_TESTCONFIG16, NULL); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret_string(void **state) +{ + torture_config_caret(state, NULL, LIBSSH_TESTCONFIG_STRING16); +} + +/** * @brief test PubkeyAcceptedKeyTypes helper function */ static void torture_config_pubkeytypes(void **state, @@ -1250,6 +1778,22 @@ static void torture_config_pubkeytypes_string(void **state) } /** + * @brief test parsing PubkeyAcceptedKAlgorithms from file + */ +static void torture_config_pubkeyalgorithms_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYALGORITHMS, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedAlgorithms from string + */ +static void torture_config_pubkeyalgorithms_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYALGORITHMS_STRING); +} + +/** * @brief Verify the configuration parser handles * missing newline in the end */ @@ -1307,17 +1851,20 @@ static void torture_config_nonewlineoneline_string(void **state) NULL, LIBSSH_TEST_NONEWLINEONELINE_STRING); } -/* ssh_config_get_cmd() does three things: +/* ssh_config_get_cmd() does these two things: * * Strips leading whitespace - * * Terminate the characted on the end of next quotes-enclosed string * * Terminate on the end of line */ static void torture_config_parser_get_cmd(void **state) { char *p = NULL, *tok = NULL; char data[256]; - - (void) state; +#ifdef __unix__ + FILE *outfile = NULL, *infile = NULL; + int pid; + char buffer[256] = {0}; +#endif + (void)state; /* Ignore leading whitespace */ strncpy(data, " \t\t string\n", sizeof(data)); @@ -1333,14 +1880,11 @@ static void torture_config_parser_get_cmd(void **state) assert_string_equal(tok, "string \t\t "); assert_int_equal(*p, '\0'); - /* should drop the quotes and split them into separate arguments */ + /* should not drop the quotes and not split them into separate arguments */ strncpy(data, "\"multi string\" something\n", sizeof(data)); p = data; tok = ssh_config_get_cmd(&p); - assert_string_equal(tok, "multi string"); - assert_int_equal(*p, ' '); - tok = ssh_config_get_cmd(&p); - assert_string_equal(tok, "something"); + assert_string_equal(tok, "\"multi string\" something"); assert_int_equal(*p, '\0'); /* But it does not split tokens by whitespace @@ -1350,6 +1894,46 @@ static void torture_config_parser_get_cmd(void **state) tok = ssh_config_get_cmd(&p); assert_string_equal(tok, "multi string something"); assert_int_equal(*p, '\0'); + + /* Commands in quotes are not treated special */ + sprintf(data, "%s%s%s%s", "\"", SOURCEDIR "/tests/unittests/hello world.sh", "\" ", "\"hello libssh\"\n"); + printf("%s\n", data); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, data); + assert_int_equal(*p, '\0'); + +#ifdef __unix__ + /* Check if the command would get correctly executed + * Use the script file "hello world.sh" to echo the first argument + * Run as <= "/workdir/hello world.sh" "hello libssh" => */ + + /* output to file and check wrong */ + outfile = fopen("output.log", "a+"); + assert_non_null(outfile); + printf("the tok is %s\n", tok); + + pid = fork(); + if (pid == -1) { + perror("fork"); + } else if (pid == 0) { + ssh_execute_command(tok, fileno(outfile), fileno(outfile)); + /* Does not return */ + } else { + /* parent + * wait child process */ + wait(NULL); + infile = fopen("output.log", "r"); + assert_non_null(infile); + p = fgets(buffer, sizeof(buffer), infile); + fclose(infile); + remove("output.log"); + assert_non_null(p); + } + + fclose(outfile); + assert_string_equal(buffer, "hello libssh"); +#endif } /* ssh_config_get_token() should behave as expected @@ -1620,12 +2204,14 @@ static void torture_config_match_pattern(void **state) static void torture_config_identity(void **state) { const char *id = NULL; + const char *cert = NULL; struct ssh_iterator *it = NULL; ssh_session session = *state; _parse_config(session, NULL, LIBSSH_TESTCONFIG_STRING13, SSH_OK); - it = ssh_list_get_iterator(session->opts.identity); + /* The identities are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); assert_non_null(it); id = it->data; /* The identities are prepended to the list so we start with second one */ @@ -1635,8 +2221,166 @@ static void torture_config_identity(void **state) assert_non_null(it); id = it->data; assert_string_equal(id, "id_rsa_one"); + + /* The certs are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + cert = it->data; + /* The certs are coming as listed in the configuration file */ + assert_string_equal(cert, "id_rsa_one-cert.pub"); + + it = it->next; + assert_non_null(it); + cert = it->data; + assert_string_equal(cert, "id_ecdsa_two-cert.pub"); + /* and that is all */ + assert_null(it->next); } +/* Make absolute path for config include + */ +static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) +{ + ssh_session session = *state; + char *result = NULL; +#ifndef _WIN32 + char h[256]; + char *user; + char *home; + + user = getenv("USER"); + if (user == NULL) { + user = getenv("LOGNAME"); + } + + /* in certain CIs there no such variables */ + if (!user) { + struct passwd *pw = getpwuid(getuid()); + if (pw){ + user = pw->pw_name; + } + } + + home = getenv("HOME"); + assert_non_null(home); +#endif + + /* Absolute path already -- should not change in any case */ + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + + /* Global is relative to /etc/ssh/ */ + result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf"); + free(result); + result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf"); + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + result = ssh_config_make_absolute(session, "my_config", 0); + if (no_sshdir_fails) { + assert_null(result); + } else { + /* The path depends on the PWD so lets skip checking the actual path here */ + assert_non_null(result); + } + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh"); + result = ssh_config_make_absolute(session, "my_config", 0); + assert_string_equal(result, "/tmp/ssh/my_config"); + free(result); + +#ifndef _WIN32 + /* Tilde expansion works only in user config */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + /* in global config its just prefixed without expansion */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/~/.ssh/config.d/*.conf"); + free(result); + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 1); + snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); + assert_string_equal(result, h); + free(result); +#endif +} + +static void torture_config_make_absolute(void **state) +{ + torture_config_make_absolute_int(state, 0); +} + +static void torture_config_make_absolute_no_sshdir(void **state) +{ + torture_config_make_absolute_int(state, 1); +} + +static void torture_config_parse_uri(void **state) +{ + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "localhost"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + /* do not want port */ + rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); +} int torture_run_tests(void) { @@ -1646,6 +2390,10 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_include_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_string, @@ -1674,14 +2422,38 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, + setup, teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, setup, teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, @@ -1698,6 +2470,12 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_identity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, teardown), }; |