From a2ebc7ea9bf72a8b74bad02adb812a9bdb1d042e Mon Sep 17 00:00:00 2001 From: Rui Li Date: Tue, 10 Mar 2026 17:28:31 -0700 Subject: [PATCH] Implement originalhost/host separation and Match support Signed-off-by: Rui Li Reviewed-by: Jakub Jelen --- include/libssh/session.h | 3 +++ src/client.c | 5 ++-- src/config.c | 35 +++++++++++++++++-------- src/misc.c | 2 ++ src/options.c | 56 +++++++++++++++++++++++++++++++++++----- src/session.c | 1 + 6 files changed, 82 insertions(+), 20 deletions(-) diff --git a/include/libssh/session.h b/include/libssh/session.h index da39df2a..9c4e9694 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -286,6 +286,9 @@ struct ssh_session_struct { int control_master; char *control_path; int address_family; + char *originalhost; /* user-supplied host for config matching */ + bool config_hostname_only; /* config hostname path: update host only, + not originalhost */ } opts; /* server options */ diff --git a/src/client.c b/src/client.c index 0d01d373..9fd79b70 100644 --- a/src/client.c +++ b/src/client.c @@ -582,9 +582,8 @@ int ssh_connect(ssh_session session) session->client = 1; if (session->opts.fd == SSH_INVALID_SOCKET && - session->opts.host == NULL && - session->opts.ProxyCommand == NULL) - { + session->opts.originalhost == NULL && + session->opts.ProxyCommand == NULL) { ssh_set_error(session, SSH_FATAL, "Hostname required"); return SSH_ERROR; } diff --git a/src/config.c b/src/config.c index 2f4ea63a..e2b3545f 100644 --- a/src/config.c +++ b/src/config.c @@ -567,7 +567,12 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } } else if (parse_entry) { /* We actually care only about the first item */ - rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false, false); + rv = ssh_config_parse_uri(cp, + &username, + &hostname, + &port, + false, + false); if (rv != SSH_OK) { ssh_set_error_invalid(session); goto out; @@ -1041,20 +1046,20 @@ static int ssh_config_parse_line_internal(ssh_session session, break; case MATCH_ORIGINALHOST: - /* Skip one argument */ + /* Here we match only one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " - "'%s' requires argument", count, p2); + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match originalhost keyword " + "requires argument", + count); SAFE_FREE(x); return -1; } + result &= + ssh_config_match(session->opts.originalhost, p, negate); args++; - SSH_LOG(SSH_LOG_TRACE, - "line %d: Unsupported Match keyword '%s', ignoring", - count, - p2); - result = 0; break; case MATCH_HOST: @@ -1067,7 +1072,11 @@ static int ssh_config_parse_line_internal(ssh_session session, SAFE_FREE(x); return -1; } - result &= ssh_config_match(session->opts.host, p, negate); + result &= ssh_config_match(session->opts.host + ? session->opts.host + : session->opts.originalhost, + p, + negate); args++; break; @@ -1155,7 +1164,9 @@ static int ssh_config_parse_line_internal(ssh_session session, int ok = 0, result = -1; *parsing = 0; - lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL; + lowerhost = (session->opts.originalhost) + ? ssh_lowercase(session->opts.originalhost) + : NULL; for (p = ssh_config_get_str_tok(&s, NULL); p != NULL && p[0] != '\0'; p = ssh_config_get_str_tok(&s, NULL)) { @@ -1182,7 +1193,9 @@ static int ssh_config_parse_line_internal(ssh_session session, if (z == NULL) { z = strdup(p); } + session->opts.config_hostname_only = true; ssh_options_set(session, SSH_OPTIONS_HOST, z); + session->opts.config_hostname_only = false; free(z); } break; diff --git a/src/misc.c b/src/misc.c index 4b8d3616..96afcdea 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1432,6 +1432,8 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) case 'h': if (session->opts.host) { x = strdup(session->opts.host); + } else if (session->opts.originalhost) { + x = strdup(session->opts.originalhost); } else { ssh_set_error(session, SSH_FATAL, "Cannot expand host"); free(buf); diff --git a/src/options.c b/src/options.c index d74cc69a..63859b00 100644 --- a/src/options.c +++ b/src/options.c @@ -23,6 +23,7 @@ */ #include "config.h" +#include #include #include #include @@ -106,6 +107,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) } } + if (src->opts.originalhost != NULL) { + new->opts.originalhost = strdup(src->opts.originalhost); + if (new->opts.originalhost == NULL) { + ssh_free(new); + return -1; + } + } + if (src->opts.bindaddr != NULL) { new->opts.bindaddr = strdup(src->opts.bindaddr); if (new->opts.bindaddr == NULL) { @@ -718,18 +727,52 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } else { char *username = NULL, *hostname = NULL; - rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true, true); - if (rc != SSH_OK) { + char *strict_hostname = NULL; + + /* Non-strict parse: reject shell metacharacters */ + rc = ssh_config_parse_uri(value, + &username, + &hostname, + NULL, + true, + false); + if (rc != SSH_OK || hostname == NULL) { + SAFE_FREE(username); + SAFE_FREE(hostname); ssh_set_error_invalid(session); return -1; } + + /* Non-strict passed: set username and originalhost */ if (username != NULL) { SAFE_FREE(session->opts.username); session->opts.username = username; } - if (hostname != NULL) { + if (!session->opts.config_hostname_only) { + SAFE_FREE(session->opts.originalhost); + session->opts.originalhost = hostname; + } else { + SAFE_FREE(hostname); + } + + /* Strict parse: set host only if valid hostname or IP */ + rc = ssh_config_parse_uri(value, + NULL, + &strict_hostname, + NULL, + true, + true); + if (rc != SSH_OK || strict_hostname == NULL) { SAFE_FREE(session->opts.host); - session->opts.host = hostname; + SAFE_FREE(strict_hostname); + if (session->opts.config_hostname_only) { + /* Config path: Hostname must be valid */ + ssh_set_error_invalid(session); + return -1; + } + } else { + SAFE_FREE(session->opts.host); + session->opts.host = strict_hostname; } } break; @@ -1646,7 +1689,8 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) switch(type) { case SSH_OPTIONS_HOST: - src = session->opts.host; + src = session->opts.host ? session->opts.host + : session->opts.originalhost; break; case SSH_OPTIONS_USER: @@ -1980,7 +2024,7 @@ int ssh_options_parse_config(ssh_session session, const char *filename) if (session == NULL) { return -1; } - if (session->opts.host == NULL) { + if (session->opts.originalhost == NULL) { ssh_set_error_invalid(session); return -1; } diff --git a/src/session.c b/src/session.c index 8e4ef0d4..13c7f79e 100644 --- a/src/session.c +++ b/src/session.c @@ -406,6 +406,7 @@ void ssh_free(ssh_session session) SAFE_FREE(session->opts.bindaddr); SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.originalhost); SAFE_FREE(session->opts.homedir); SAFE_FREE(session->opts.sshdir); SAFE_FREE(session->opts.knownhosts);