misc: Add support for %j and %C percent expand

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
This commit is contained in:
Jakub Jelen
2026-01-27 21:39:56 +01:00
parent 59ed66b684
commit 5b0cee7c1b
4 changed files with 144 additions and 28 deletions

View File

@@ -250,6 +250,7 @@ struct ssh_session_struct {
struct ssh_list *certificate_non_exp;
struct ssh_list *proxy_jumps;
struct ssh_list *proxy_jumps_user_cb;
char *proxy_jumps_str;
char *username;
char *host;
char *bindaddr; /* bind the client to an ip addr */

View File

@@ -493,6 +493,10 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
bool parse_entry = do_parsing;
bool libssh_proxy_jump = ssh_libssh_proxy_jumps();
if (do_parsing) {
SAFE_FREE(session->opts.proxy_jumps_str);
ssh_proxyjumps_free(session->opts.proxy_jumps);
}
/* Special value none disables the proxy */
cmp = strcasecmp(s, "none");
if (cmp == 0) {
@@ -509,6 +513,17 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
return SSH_ERROR;
}
if (do_parsing) {
/* Store the whole string in sesion */
SAFE_FREE(session->opts.proxy_jumps_str);
session->opts.proxy_jumps_str = strdup(s);
if (session->opts.proxy_jumps_str == NULL) {
free(c);
ssh_set_error_oom(session);
return SSH_ERROR;
}
}
cp = c;
do {
endp = strchr(cp, ',');

View File

@@ -490,6 +490,38 @@ char *ssh_hostport(const char *host, int port)
return dest;
}
static char *
ssh_get_hexa_internal(const unsigned char *what, size_t len, bool colons)
{
const char h[] = "0123456789abcdef";
char *hexa = NULL;
size_t i;
size_t bytes_per_byte = 2 + (colons ? 1 : 0);
size_t hlen = len * bytes_per_byte;
if (len > (UINT_MAX - 1) / bytes_per_byte) {
return NULL;
}
hexa = calloc(hlen + 1, sizeof(char));
if (hexa == NULL) {
return NULL;
}
for (i = 0; i < len; i++) {
hexa[i * bytes_per_byte] = h[(what[i] >> 4) & 0xF];
hexa[i * bytes_per_byte + 1] = h[what[i] & 0xF];
if (colons) {
hexa[i * bytes_per_byte + 2] = ':';
}
}
if (colons) {
hexa[hlen - 1] = '\0';
}
return hexa;
}
/**
* @brief Convert a buffer into a colon separated hex string.
* The caller has to free the memory.
@@ -505,28 +537,7 @@ char *ssh_hostport(const char *host, int port)
*/
char *ssh_get_hexa(const unsigned char *what, size_t len)
{
const char h[] = "0123456789abcdef";
char *hexa = NULL;
size_t i;
size_t hlen = len * 3;
if (len > (UINT_MAX - 1) / 3) {
return NULL;
}
hexa = malloc(hlen + 1);
if (hexa == NULL) {
return NULL;
}
for (i = 0; i < len; i++) {
hexa[i * 3] = h[(what[i] >> 4) & 0xF];
hexa[i * 3 + 1] = h[what[i] & 0xF];
hexa[i * 3 + 2] = ':';
}
hexa[hlen - 1] = '\0';
return hexa;
return ssh_get_hexa_internal(what, len, true);
}
/**
@@ -1245,15 +1256,93 @@ char *ssh_get_local_hostname(void)
return strdup(host);
}
static char *get_connection_hash(ssh_session session)
{
unsigned char conn_hash[SHA_DIGEST_LENGTH];
char *local_hostname = NULL;
SHACTX ctx = sha1_init();
char strport[10] = {0};
unsigned int port;
int rc;
if (session == NULL) {
return NULL;
}
if (ctx == NULL) {
goto err;
}
/* Local hostname %l */
local_hostname = ssh_get_local_hostname();
if (local_hostname == NULL) {
goto err;
}
rc = sha1_update(ctx, local_hostname, strlen(local_hostname));
if (rc != SSH_OK) {
goto err;
}
SAFE_FREE(local_hostname);
/* Remote hostname %h */
rc = sha1_update(ctx, session->opts.host, strlen(session->opts.host));
if (rc != SSH_OK) {
goto err;
}
/* Remote port %p */
ssh_options_get_port(session, &port);
snprintf(strport, sizeof(strport), "%d", port);
rc = sha1_update(ctx, strport, strlen(strport));
if (rc != SSH_OK) {
goto err;
}
/* The remote username %r */
rc = sha1_update(ctx,
session->opts.username,
strlen(session->opts.username));
if (rc != SSH_OK) {
goto err;
}
/* ProxyJump */
if (session->opts.proxy_jumps_str != NULL) {
rc = sha1_update(ctx,
session->opts.proxy_jumps_str,
strlen(session->opts.proxy_jumps_str));
}
if (rc != SSH_OK) {
goto err;
}
/* Frees context */
rc = sha1_final(conn_hash, ctx);
if (rc != SSH_OK) {
goto err;
}
return ssh_get_hexa_internal(conn_hash, SHA_DIGEST_LENGTH, false);
err:
free(local_hostname);
sha1_ctx_free(ctx);
return NULL;
}
/** @internal
* @brief expands a string in function of session options
*
* @param[in] s Format string to expand. Known parameters:
* %d user home directory (~)
* %h target host name
* %u local username
* %l local hostname
* %r remote username
* %p remote port
* - %d user home directory (~)
* - %h target host name
* - %u local username
* - %l local hostname
* - %r remote username
* - %p remote port
* - %j proxyjump string
* - %C Hash of %l%h%p%r%j
*
* @returns Expanded string. The caller needs to free the memory using
* ssh_string_free_char().
*
@@ -1355,6 +1444,16 @@ char *ssh_path_expand_escape(ssh_session session, const char *s)
x = strdup(tmp);
break;
}
case 'j':
if (session->opts.proxy_jumps_str != NULL) {
x = strdup(session->opts.proxy_jumps_str);
} else {
x = strdup("");
}
break;
case 'C':
x = get_connection_hash(session);
break;
default:
ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected");
free(buf);

View File

@@ -384,6 +384,7 @@ void ssh_free(ssh_session session)
ssh_proxyjumps_free(session->opts.proxy_jumps);
SSH_LIST_FREE(session->opts.proxy_jumps);
SSH_LIST_FREE(session->opts.proxy_jumps_user_cb);
SAFE_FREE(session->opts.proxy_jumps_str);
while ((b = ssh_list_pop_head(struct ssh_buffer_struct *,
session->out_queue)) != NULL) {