Implement originalhost/host separation and Match support

Signed-off-by: Rui Li <ruili3422@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
Rui Li
2026-03-10 17:28:31 -07:00
committed by Jakub Jelen
parent 1ab8a35c5d
commit a2ebc7ea9b
6 changed files with 82 additions and 20 deletions

View File

@@ -286,6 +286,9 @@ struct ssh_session_struct {
int control_master; int control_master;
char *control_path; char *control_path;
int address_family; int address_family;
char *originalhost; /* user-supplied host for config matching */
bool config_hostname_only; /* config hostname path: update host only,
not originalhost */
} opts; } opts;
/* server options */ /* server options */

View File

@@ -582,9 +582,8 @@ int ssh_connect(ssh_session session)
session->client = 1; session->client = 1;
if (session->opts.fd == SSH_INVALID_SOCKET && if (session->opts.fd == SSH_INVALID_SOCKET &&
session->opts.host == NULL && session->opts.originalhost == NULL &&
session->opts.ProxyCommand == NULL) session->opts.ProxyCommand == NULL) {
{
ssh_set_error(session, SSH_FATAL, "Hostname required"); ssh_set_error(session, SSH_FATAL, "Hostname required");
return SSH_ERROR; return SSH_ERROR;
} }

View File

@@ -567,7 +567,12 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
} }
} else if (parse_entry) { } else if (parse_entry) {
/* We actually care only about the first item */ /* 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) { if (rv != SSH_OK) {
ssh_set_error_invalid(session); ssh_set_error_invalid(session);
goto out; goto out;
@@ -1041,20 +1046,20 @@ static int ssh_config_parse_line_internal(ssh_session session,
break; break;
case MATCH_ORIGINALHOST: case MATCH_ORIGINALHOST:
/* Skip one argument */ /* Here we match only one argument */
p = ssh_config_get_str_tok(&s, NULL); p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL || p[0] == '\0') { if (p == NULL || p[0] == '\0') {
SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " ssh_set_error(session,
"'%s' requires argument", count, p2); SSH_FATAL,
"line %d: ERROR - Match originalhost keyword "
"requires argument",
count);
SAFE_FREE(x); SAFE_FREE(x);
return -1; return -1;
} }
result &=
ssh_config_match(session->opts.originalhost, p, negate);
args++; args++;
SSH_LOG(SSH_LOG_TRACE,
"line %d: Unsupported Match keyword '%s', ignoring",
count,
p2);
result = 0;
break; break;
case MATCH_HOST: case MATCH_HOST:
@@ -1067,7 +1072,11 @@ static int ssh_config_parse_line_internal(ssh_session session,
SAFE_FREE(x); SAFE_FREE(x);
return -1; 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++; args++;
break; break;
@@ -1155,7 +1164,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
int ok = 0, result = -1; int ok = 0, result = -1;
*parsing = 0; *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); for (p = ssh_config_get_str_tok(&s, NULL);
p != NULL && p[0] != '\0'; p != NULL && p[0] != '\0';
p = ssh_config_get_str_tok(&s, NULL)) { 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) { if (z == NULL) {
z = strdup(p); z = strdup(p);
} }
session->opts.config_hostname_only = true;
ssh_options_set(session, SSH_OPTIONS_HOST, z); ssh_options_set(session, SSH_OPTIONS_HOST, z);
session->opts.config_hostname_only = false;
free(z); free(z);
} }
break; break;

View File

@@ -1432,6 +1432,8 @@ char *ssh_path_expand_escape(ssh_session session, const char *s)
case 'h': case 'h':
if (session->opts.host) { if (session->opts.host) {
x = strdup(session->opts.host); x = strdup(session->opts.host);
} else if (session->opts.originalhost) {
x = strdup(session->opts.originalhost);
} else { } else {
ssh_set_error(session, SSH_FATAL, "Cannot expand host"); ssh_set_error(session, SSH_FATAL, "Cannot expand host");
free(buf); free(buf);

View File

@@ -23,6 +23,7 @@
*/ */
#include "config.h" #include "config.h"
#include <ctype.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -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) { if (src->opts.bindaddr != NULL) {
new->opts.bindaddr = strdup(src->opts.bindaddr); new->opts.bindaddr = strdup(src->opts.bindaddr);
if (new->opts.bindaddr == NULL) { if (new->opts.bindaddr == NULL) {
@@ -718,18 +727,52 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
return -1; return -1;
} else { } else {
char *username = NULL, *hostname = NULL; char *username = NULL, *hostname = NULL;
rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true, true); char *strict_hostname = NULL;
if (rc != SSH_OK) {
/* 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); ssh_set_error_invalid(session);
return -1; return -1;
} }
/* Non-strict passed: set username and originalhost */
if (username != NULL) { if (username != NULL) {
SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.username);
session->opts.username = 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); 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; break;
@@ -1646,7 +1689,8 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
switch(type) switch(type)
{ {
case SSH_OPTIONS_HOST: case SSH_OPTIONS_HOST:
src = session->opts.host; src = session->opts.host ? session->opts.host
: session->opts.originalhost;
break; break;
case SSH_OPTIONS_USER: case SSH_OPTIONS_USER:
@@ -1980,7 +2024,7 @@ int ssh_options_parse_config(ssh_session session, const char *filename)
if (session == NULL) { if (session == NULL) {
return -1; return -1;
} }
if (session->opts.host == NULL) { if (session->opts.originalhost == NULL) {
ssh_set_error_invalid(session); ssh_set_error_invalid(session);
return -1; return -1;
} }

View File

@@ -406,6 +406,7 @@ void ssh_free(ssh_session session)
SAFE_FREE(session->opts.bindaddr); SAFE_FREE(session->opts.bindaddr);
SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.username);
SAFE_FREE(session->opts.host); SAFE_FREE(session->opts.host);
SAFE_FREE(session->opts.originalhost);
SAFE_FREE(session->opts.homedir); SAFE_FREE(session->opts.homedir);
SAFE_FREE(session->opts.sshdir); SAFE_FREE(session->opts.sshdir);
SAFE_FREE(session->opts.knownhosts); SAFE_FREE(session->opts.knownhosts);