From c78d2bb8fb9f9747d4db2773f63cb53aa88de729 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 28 Jan 2026 14:23:39 +0100 Subject: [PATCH] test: Verify expand characters work as expected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakub Jelen Reviewed-by: Pavol Žáčik Reviewed-by: Andreas Schneider --- src/options.c | 1 - tests/client/torture_client_config.c | 225 +++++++++++++++++++++++++++ tests/etc/group.in | 2 +- tests/etc/passwd.in | 2 +- 4 files changed, 227 insertions(+), 3 deletions(-) diff --git a/src/options.c b/src/options.c index 93136c9b..a19a8293 100644 --- a/src/options.c +++ b/src/options.c @@ -1197,7 +1197,6 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - ssh_proxyjumps_free(session->opts.proxy_jumps); rc = ssh_config_parse_proxy_jump(session, v, true); if (rc != SSH_OK) { return SSH_ERROR; diff --git a/tests/client/torture_client_config.c b/tests/client/torture_client_config.c index 5ac22a4c..f532f0df 100644 --- a/tests/client/torture_client_config.c +++ b/tests/client/torture_client_config.c @@ -6,6 +6,7 @@ #include #include "torture.h" #include "libssh/session.h" +#include "libssh/options.h" #include "libssh/misc.h" #define LIBSSH_SSH_CONFIG "libssh_config" @@ -59,6 +60,23 @@ static int setup_config_files(void **state) return 0; } +static int setup_session(void **state) +{ + struct torture_state *s = *state; + int verbosity; + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + setenv("NSS_WRAPPER_HOSTNAME", "client.libssh.site", 1); + + return 0; +} + static int teardown(void **state) { struct torture_state *s = *state; @@ -80,6 +98,16 @@ static int teardown(void **state) return 0; } +static int teardown_session(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + /* This tests makes sure that parsing both system-wide and per-user * configuration files retains OpenSSH semantics (the per-user overrides * the system-wide values). @@ -210,10 +238,207 @@ static void torture_client_config_suppress(void **state) assert_string_equal(s->ssh.session->opts.username, "bob"); } +static void torture_client_config_expand(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + /* TEST: user home directory */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%d"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + BINARYDIR "/tests/home"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: target host name */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%h"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, TORTURE_SSH_SERVER); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%u"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "root"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local hostname */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "client.libssh.site"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "alice"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%r"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "alice"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote port */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "2222"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%p"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "2222"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: empty proxyjump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* No proxyjump string should not explode */ + assert_string_equal(s->ssh.session->opts.knownhosts, ""); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: proxyjump string present */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + "user@" TORTURE_SSH_SERVER ":22"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYJUMP, "none"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-"); + + + /* TEST: hash of %l%h%p%r%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "133e3957ff9d01fdcf1f6c7f83325a8ce49bf850"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-user@" + TORTURE_SSH_SERVER ":22"); + + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: hash of %l%h%p%r%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "adf0b7c4e71a0fee85fd97506507ba8591f3663b"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { + /* Keep this first -- following setup is changing user to bob, which we + * do not want */ + cmocka_unit_test_setup_teardown(torture_client_config_expand, + setup_session, + teardown_session), cmocka_unit_test_setup_teardown(torture_client_config_system, setup_config_files, teardown), diff --git a/tests/etc/group.in b/tests/etc/group.in index 8e13294b..df5ae8ab 100644 --- a/tests/etc/group.in +++ b/tests/etc/group.in @@ -2,4 +2,4 @@ users:x:9000: sshd:x:65531: nobody:x:65533: nogroup:x:65534:nobody -root:x:65532: +root:x:0: diff --git a/tests/etc/passwd.in b/tests/etc/passwd.in index 87dcb985..fff78780 100644 --- a/tests/etc/passwd.in +++ b/tests/etc/passwd.in @@ -5,5 +5,5 @@ doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/bin/sh frank:x:5003:9000:doe gecos:@HOMEDIR@/frank:/bin/sh sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false -root:x:65534:65532:root gecos:@HOMEDIR@:/bin/false +root:x:0:0:root gecos:@HOMEDIR@:/bin/false @LOCAL_USER@:x:@LOCAL_UID@:9000:local user:@HOMEDIR@:/bin/false