mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-03-24 20:40:09 +09:00
Compare commits
5 Commits
8782fcec18
...
4dfcdd96b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dfcdd96b8 | ||
|
|
9d36b9dd81 | ||
|
|
afa21334b4 | ||
|
|
a2ebc7ea9b | ||
|
|
1ab8a35c5d |
@@ -522,7 +522,7 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks;
|
||||
* verifies that the callback pointer exists
|
||||
* @param p callback pointer
|
||||
* @param c callback name
|
||||
* @returns nonzero if callback can be called
|
||||
* @return nonzero if callback can be called
|
||||
*/
|
||||
#define ssh_callbacks_exists(p,c) (\
|
||||
(p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \
|
||||
|
||||
@@ -54,6 +54,11 @@ int ssh_config_get_yesno(char **str, int notfound);
|
||||
* be stored or NULL if we do not care about the result.
|
||||
* @param[in] ignore_port Set to true if we should not attempt to parse
|
||||
* port number.
|
||||
* @param[in] strict Set to true to validate hostname against RFC1035
|
||||
* (for resolving to a real host).
|
||||
* Set to false to only reject shell metacharacters
|
||||
* (allowing config aliases with non-RFC1035 chars
|
||||
* like underscores, resolved later via Hostname).
|
||||
*
|
||||
* @returns SSH_OK if the provided string is in format of SSH URI,
|
||||
* SSH_ERROR on failure
|
||||
@@ -62,7 +67,8 @@ int ssh_config_parse_uri(const char *tok,
|
||||
char **username,
|
||||
char **hostname,
|
||||
char **port,
|
||||
bool ignore_port);
|
||||
bool ignore_port,
|
||||
bool strict);
|
||||
|
||||
/**
|
||||
* @brief: Parse the ProxyJump configuration line and if parsing,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
22
src/auth.c
22
src/auth.c
@@ -1835,6 +1835,14 @@ int ssh_userauth_agent_pubkey(ssh_session session,
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Allocates memory for keyboard interactive auth structure.
|
||||
*
|
||||
* @return A newly allocated ssh_kbdint structure `kbd` on success, NULL on failure.
|
||||
* The caller is responsible for freeing allocated memory.
|
||||
*/
|
||||
ssh_kbdint ssh_kbdint_new(void)
|
||||
{
|
||||
ssh_kbdint kbd;
|
||||
@@ -1847,7 +1855,11 @@ ssh_kbdint ssh_kbdint_new(void)
|
||||
return kbd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory for keyboard interactive auth structure.
|
||||
*
|
||||
* @param[in] kbd The keyboard interactive structure to free.
|
||||
*/
|
||||
void ssh_kbdint_free(ssh_kbdint kbd)
|
||||
{
|
||||
size_t i, n;
|
||||
@@ -1885,6 +1897,14 @@ void ssh_kbdint_free(ssh_kbdint kbd)
|
||||
SAFE_FREE(kbd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean a keyboard interactive auth structure.
|
||||
*
|
||||
* Clears structure's fields and resets nanswers and nprompts to 0, allowing
|
||||
* reuse.
|
||||
*
|
||||
* @param[in] kbd The keyboard interactive struct to clean
|
||||
*/
|
||||
void ssh_kbdint_clean(ssh_kbdint kbd)
|
||||
{
|
||||
size_t i, n;
|
||||
|
||||
@@ -327,7 +327,7 @@ static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int reve
|
||||
/** @internal
|
||||
* @brief returns the current poll handle, or creates it
|
||||
* @param sshbind the ssh_bind object
|
||||
* @returns a ssh_poll handle suitable for operation
|
||||
* @return a ssh_poll handle suitable for operation
|
||||
*/
|
||||
ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind)
|
||||
{
|
||||
|
||||
@@ -680,8 +680,8 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
* @brief Parse configuration string and set the options to the given bind
|
||||
* session
|
||||
*
|
||||
* @params[in] bind The ssh bind session
|
||||
* @params[in] input Null terminated string containing the configuration
|
||||
* @param[in] bind The ssh bind session
|
||||
* @param[in] input Null terminated string containing the configuration
|
||||
*
|
||||
* @warning Options set via this function may be overridden if a configuration
|
||||
* file is parsed afterwards (e.g., by an implicit call to
|
||||
@@ -690,7 +690,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
* It is the caller’s responsibility to ensure the correct order of
|
||||
* API calls if explicit options must take precedence.
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* @return SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
*/
|
||||
int ssh_bind_config_parse_string(ssh_bind bind, const char *input)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
38
src/config.c
38
src/config.c
@@ -544,6 +544,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
&jump_host->username,
|
||||
&jump_host->hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
@@ -566,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);
|
||||
rv = ssh_config_parse_uri(cp,
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -582,7 +588,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
}
|
||||
} else {
|
||||
/* The rest is just sanity-checked to avoid failures later */
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -1040,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:
|
||||
@@ -1066,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;
|
||||
|
||||
@@ -1154,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)) {
|
||||
@@ -1181,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;
|
||||
|
||||
@@ -172,7 +172,8 @@ int ssh_config_parse_uri(const char *tok,
|
||||
char **username,
|
||||
char **hostname,
|
||||
char **port,
|
||||
bool ignore_port)
|
||||
bool ignore_port,
|
||||
bool strict)
|
||||
{
|
||||
char *endp = NULL;
|
||||
long port_n;
|
||||
@@ -243,6 +244,7 @@ int ssh_config_parse_uri(const char *tok,
|
||||
if (*hostname == NULL) {
|
||||
goto error;
|
||||
}
|
||||
if (strict) {
|
||||
/* if not an ip, check syntax */
|
||||
rc = ssh_is_ipaddr(*hostname);
|
||||
if (rc == 0) {
|
||||
@@ -251,6 +253,23 @@ int ssh_config_parse_uri(const char *tok,
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Reject shell metacharacters to allow config aliases with
|
||||
* non-RFC1035 chars (e.g. %, _). Modeled on OpenSSH's
|
||||
* valid_hostname() in ssh.c. */
|
||||
const char *c = NULL;
|
||||
if ((*hostname)[0] == '-') {
|
||||
goto error;
|
||||
}
|
||||
for (c = *hostname; *c != '\0'; c++) {
|
||||
char *is_meta = strchr("'`\"$\\;&<>|(){},", *c);
|
||||
int is_space = isspace((unsigned char)*c);
|
||||
int is_ctrl = iscntrl((unsigned char)*c);
|
||||
if (is_meta != NULL || is_space || is_ctrl) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Skip also the closing bracket */
|
||||
if (*endp == ']') {
|
||||
|
||||
14
src/misc.c
14
src/misc.c
@@ -208,6 +208,12 @@ struct tm *ssh_localtime(const time_t *timer, struct tm *result)
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get username from the calling process.
|
||||
*
|
||||
* @return An allocated string with the user on success, NULL on failure. The
|
||||
* caller is responsible for freeing returned string.
|
||||
*/
|
||||
char *ssh_get_local_username(void)
|
||||
{
|
||||
DWORD size = 0;
|
||||
@@ -357,6 +363,12 @@ int ssh_dir_writeable(const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get username from the calling process.
|
||||
*
|
||||
* @return An allocated string with the name on success, NULL on failure. The
|
||||
* caller is responsible for freeing returned string.
|
||||
*/
|
||||
char *ssh_get_local_username(void)
|
||||
{
|
||||
struct passwd pwd;
|
||||
@@ -1432,6 +1444,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);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -444,7 +444,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p,
|
||||
/** @internal
|
||||
* @brief returns the poll handle corresponding to the socket,
|
||||
* creates it if it does not exist.
|
||||
* @returns allocated and initialized ssh_poll_handle object
|
||||
*
|
||||
* @return allocated and initialized ssh_poll_handle object
|
||||
*/
|
||||
ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,6 @@ static struct ssh_threads_callbacks_struct *user_callbacks = NULL;
|
||||
/** @internal
|
||||
* @brief inits the threading with the backend cryptographic libraries
|
||||
*/
|
||||
|
||||
int ssh_threads_init(void)
|
||||
{
|
||||
static int threads_initialized = 0;
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
project(fuzzing CXX)
|
||||
|
||||
# Build SSH server mock helper as object library
|
||||
add_library(ssh_server_mock_obj OBJECT ssh_server_mock.c)
|
||||
target_link_libraries(ssh_server_mock_obj PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
macro(fuzzer name)
|
||||
add_executable(${name} ${name}.c)
|
||||
target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
# Add ssh_server_mock dependency for scp and sftp fuzzers
|
||||
if (${name} STREQUAL "ssh_scp_fuzzer" OR ${name} STREQUAL "ssh_sftp_fuzzer")
|
||||
target_sources(${name} PRIVATE $<TARGET_OBJECTS:ssh_server_mock_obj>)
|
||||
target_link_libraries(${name} PRIVATE pthread)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set_target_properties(${name}
|
||||
PROPERTIES
|
||||
@@ -36,6 +47,7 @@ fuzzer(ssh_pubkey_fuzzer)
|
||||
fuzzer(ssh_sftp_attr_fuzzer)
|
||||
fuzzer(ssh_sshsig_fuzzer)
|
||||
if (WITH_SERVER)
|
||||
fuzzer(ssh_scp_fuzzer)
|
||||
fuzzer(ssh_server_fuzzer)
|
||||
fuzzer(ssh_bind_config_fuzzer)
|
||||
endif (WITH_SERVER)
|
||||
|
||||
206
tests/fuzz/ssh_scp_fuzzer.c
Normal file
206
tests/fuzz/ssh_scp_fuzzer.c
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/scp.h>
|
||||
|
||||
#include "nallocinc.c"
|
||||
#include "ssh_server_mock.h"
|
||||
|
||||
static void _fuzz_finalize(void)
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
nalloc_init(*argv[0]);
|
||||
ssh_init();
|
||||
atexit(_fuzz_finalize);
|
||||
ssh_mock_write_hostkey(SSH_MOCK_HOSTKEY_PATH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function to test one cipher/HMAC combination */
|
||||
static int test_scp_with_cipher(const uint8_t *data,
|
||||
size_t size,
|
||||
const char *cipher,
|
||||
const char *hmac)
|
||||
{
|
||||
int socket_fds[2] = {-1, -1};
|
||||
ssh_session client_session = NULL;
|
||||
ssh_scp scp = NULL, scp_recursive = NULL;
|
||||
char buf[256] = {0};
|
||||
pthread_t srv_thread;
|
||||
|
||||
/* Configure mock SSH server with fuzzer data */
|
||||
struct ssh_mock_server_config server_config = {
|
||||
.protocol_data = data,
|
||||
.protocol_data_size = size,
|
||||
.exec_callback = ssh_mock_send_raw_data,
|
||||
.subsystem_callback = NULL,
|
||||
.callback_userdata = NULL,
|
||||
.cipher = cipher,
|
||||
.hmac = hmac,
|
||||
.server_socket = -1,
|
||||
.client_socket = -1,
|
||||
.server_ready = false,
|
||||
.server_error = false,
|
||||
};
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
server_config.server_socket = socket_fds[0];
|
||||
server_config.client_socket = socket_fds[1];
|
||||
|
||||
if (ssh_mock_server_start(&server_config, &srv_thread) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
client_session = ssh_new();
|
||||
if (client_session == NULL) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
/* Configure client with specified cipher/HMAC */
|
||||
ssh_options_set(client_session, SSH_OPTIONS_FD, &socket_fds[1]);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HOST, "localhost");
|
||||
ssh_options_set(client_session, SSH_OPTIONS_USER, "fuzz");
|
||||
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_C_S, cipher);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_S_C, cipher);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HMAC_C_S, hmac);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HMAC_S_C, hmac);
|
||||
|
||||
/* Set timeout for operations (1 second) */
|
||||
long timeout = 1;
|
||||
ssh_options_set(client_session, SSH_OPTIONS_TIMEOUT, &timeout);
|
||||
|
||||
if (ssh_connect(client_session) != SSH_OK) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (ssh_userauth_none(client_session, NULL) != SSH_AUTH_SUCCESS) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
scp = ssh_scp_new(client_session, SSH_SCP_READ, "/tmp/fuzz");
|
||||
if (scp == NULL) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (ssh_scp_init(scp) != SSH_OK) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
size_t copy_size = size < sizeof(buf) ? size : sizeof(buf);
|
||||
memcpy(buf, data, copy_size);
|
||||
}
|
||||
|
||||
/* Fuzz all SCP API functions in read mode */
|
||||
ssh_scp_pull_request(scp);
|
||||
ssh_scp_request_get_filename(scp);
|
||||
ssh_scp_request_get_permissions(scp);
|
||||
ssh_scp_request_get_size64(scp);
|
||||
ssh_scp_request_get_size(scp);
|
||||
ssh_scp_request_get_warning(scp);
|
||||
ssh_scp_accept_request(scp);
|
||||
ssh_scp_deny_request(scp, "Denied by fuzzer");
|
||||
ssh_scp_read(scp, buf, sizeof(buf));
|
||||
|
||||
/* Final fuzz of scp pull request after all the calls */
|
||||
ssh_scp_pull_request(scp);
|
||||
|
||||
/* Fuzz SCP in write/upload + recursive directory mode. */
|
||||
scp_recursive = ssh_scp_new(client_session,
|
||||
SSH_SCP_WRITE | SSH_SCP_RECURSIVE,
|
||||
"/tmp/fuzz-recursive");
|
||||
if (scp_recursive != NULL) {
|
||||
if (ssh_scp_init(scp_recursive) == SSH_OK) {
|
||||
ssh_scp_push_directory(scp_recursive, "fuzz-dir", 0755);
|
||||
ssh_scp_push_file(scp_recursive, "fuzz-file", sizeof(buf), 0644);
|
||||
ssh_scp_write(scp_recursive, buf, sizeof(buf));
|
||||
ssh_scp_leave_directory(scp_recursive);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_thread:
|
||||
pthread_join(srv_thread, NULL);
|
||||
|
||||
cleanup:
|
||||
if (scp_recursive != NULL) {
|
||||
ssh_scp_close(scp_recursive);
|
||||
ssh_scp_free(scp_recursive);
|
||||
}
|
||||
if (scp) {
|
||||
ssh_scp_close(scp);
|
||||
ssh_scp_free(scp);
|
||||
}
|
||||
if (client_session) {
|
||||
ssh_disconnect(client_session);
|
||||
ssh_free(client_session);
|
||||
}
|
||||
if (socket_fds[0] >= 0)
|
||||
close(socket_fds[0]);
|
||||
if (socket_fds[1] >= 0)
|
||||
close(socket_fds[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
assert(nalloc_start(data, size) > 0);
|
||||
|
||||
/* Test all cipher/HMAC combinations exhaustively */
|
||||
const char *ciphers[] = {
|
||||
"none",
|
||||
"aes128-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-cbc",
|
||||
};
|
||||
|
||||
const char *hmacs[] = {
|
||||
"none",
|
||||
"hmac-sha1",
|
||||
"hmac-sha2-256",
|
||||
};
|
||||
|
||||
int num_ciphers = sizeof(ciphers) / sizeof(ciphers[0]);
|
||||
int num_hmacs = sizeof(hmacs) / sizeof(hmacs[0]);
|
||||
|
||||
for (int i = 0; i < num_ciphers; i++) {
|
||||
for (int j = 0; j < num_hmacs; j++) {
|
||||
test_scp_with_cipher(data, size, ciphers[i], hmacs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
nalloc_end();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
C0644 50 ../../../etc/passwd
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 dir/file.txt
|
||||
@@ -0,0 +1 @@
|
||||
C 100 test
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 ..
|
||||
@@ -0,0 +1 @@
|
||||
C0755 1024 executable.sh
|
||||
@@ -0,0 +1 @@
|
||||
C0644 999999999999 huge.dat
|
||||
@@ -0,0 +1 @@
|
||||
T1234567890 0 1234567890 0
|
||||
@@ -0,0 +1 @@
|
||||
C0644 100 test.txt
|
||||
@@ -0,0 +1 @@
|
||||
Warning: Test warning
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 .
|
||||
@@ -0,0 +1 @@
|
||||
E
|
||||
@@ -0,0 +1 @@
|
||||
Error: Test error
|
||||
@@ -0,0 +1 @@
|
||||
Xunknown command
|
||||
@@ -0,0 +1 @@
|
||||
C0644 test
|
||||
@@ -0,0 +1 @@
|
||||
D0755 0 mydir
|
||||
@@ -0,0 +1 @@
|
||||
C0644 abc test
|
||||
262
tests/fuzz/ssh_server_mock.c
Normal file
262
tests/fuzz/ssh_server_mock.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ssh_server_mock.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
|
||||
/* Fixed ed25519 key for all mock servers */
|
||||
const char *ssh_mock_ed25519_key_pem =
|
||||
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
||||
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
|
||||
"QyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zAAAAJgbsYq1G7GK\n"
|
||||
"tQAAAAtzc2gtZWQyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zA\n"
|
||||
"AAAEAkGaLvQwKMbGVRk2M8cz7gqWvpBKuHkuekJxIBQrUJl2kU7z8l9iUiqDr6+rHW8MpY\n"
|
||||
"OrEkfHDi0GqdB+I6N/bMAAAAEGZ1enotZWQyNTUxOS1rZXkBAgMEBQ==\n"
|
||||
"-----END OPENSSH PRIVATE KEY-----\n";
|
||||
|
||||
/* Internal server session data */
|
||||
struct mock_session_data {
|
||||
ssh_channel channel;
|
||||
struct ssh_mock_server_config *config;
|
||||
};
|
||||
|
||||
/* Auth callback - always accepts "none" auth */
|
||||
static int mock_auth_none(ssh_session session, const char *user, void *userdata)
|
||||
{
|
||||
(void)session;
|
||||
(void)user;
|
||||
(void)userdata;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
/* Channel open callback */
|
||||
static ssh_channel mock_channel_open(ssh_session session, void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
sdata->channel = ssh_channel_new(session);
|
||||
return sdata->channel;
|
||||
}
|
||||
|
||||
/* Exec request callback - for SCP */
|
||||
static int mock_channel_exec(ssh_session session,
|
||||
ssh_channel channel,
|
||||
const char *command,
|
||||
void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
(void)session;
|
||||
(void)command;
|
||||
|
||||
if (sdata->config->exec_callback) {
|
||||
return sdata->config->exec_callback(channel,
|
||||
sdata->config->protocol_data,
|
||||
sdata->config->protocol_data_size,
|
||||
sdata->config->callback_userdata);
|
||||
}
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Subsystem request callback - for SFTP */
|
||||
static int mock_channel_subsystem(ssh_session session,
|
||||
ssh_channel channel,
|
||||
const char *subsystem,
|
||||
void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
(void)session;
|
||||
(void)subsystem;
|
||||
|
||||
if (sdata->config->subsystem_callback) {
|
||||
return sdata->config->subsystem_callback(
|
||||
channel,
|
||||
sdata->config->protocol_data,
|
||||
sdata->config->protocol_data_size,
|
||||
sdata->config->callback_userdata);
|
||||
}
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Server thread implementation */
|
||||
static void *server_thread_func(void *arg)
|
||||
{
|
||||
struct ssh_mock_server_config *config =
|
||||
(struct ssh_mock_server_config *)arg;
|
||||
ssh_bind sshbind = NULL;
|
||||
ssh_session session = NULL;
|
||||
ssh_event event = NULL;
|
||||
struct mock_session_data sdata = {0};
|
||||
sdata.config = config;
|
||||
|
||||
struct ssh_server_callbacks_struct server_cb = {
|
||||
.userdata = &sdata,
|
||||
.auth_none_function = mock_auth_none,
|
||||
.channel_open_request_session_function = mock_channel_open,
|
||||
};
|
||||
|
||||
struct ssh_channel_callbacks_struct channel_cb = {
|
||||
.userdata = &sdata,
|
||||
.channel_exec_request_function = mock_channel_exec,
|
||||
.channel_subsystem_request_function = mock_channel_subsystem,
|
||||
};
|
||||
|
||||
bool no = false;
|
||||
|
||||
sshbind = ssh_bind_new();
|
||||
if (sshbind == NULL) {
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
ssh_bind_free(sshbind);
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *cipher = config->cipher ? config->cipher : "aes128-ctr";
|
||||
const char *hmac = config->hmac ? config->hmac : "hmac-sha1";
|
||||
|
||||
ssh_bind_options_set(sshbind,
|
||||
SSH_BIND_OPTIONS_HOSTKEY,
|
||||
SSH_MOCK_HOSTKEY_PATH);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, cipher);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, cipher);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, hmac);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, hmac);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no);
|
||||
|
||||
ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE);
|
||||
ssh_callbacks_init(&server_cb);
|
||||
ssh_set_server_callbacks(session, &server_cb);
|
||||
|
||||
if (ssh_bind_accept_fd(sshbind, session, config->server_socket) != SSH_OK) {
|
||||
ssh_free(session);
|
||||
ssh_bind_free(sshbind);
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config->server_ready = true;
|
||||
|
||||
event = ssh_event_new();
|
||||
if (event == NULL) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
ssh_bind_free(sshbind);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ssh_handle_key_exchange(session) == SSH_OK) {
|
||||
ssh_event_add_session(event, session);
|
||||
|
||||
for (int i = 0; i < 50 && !sdata.channel; i++) {
|
||||
ssh_event_dopoll(event, 1);
|
||||
}
|
||||
|
||||
if (sdata.channel) {
|
||||
ssh_callbacks_init(&channel_cb);
|
||||
ssh_set_channel_callbacks(sdata.channel, &channel_cb);
|
||||
|
||||
int max_iterations = 30;
|
||||
for (int iter = 0; iter < max_iterations &&
|
||||
!ssh_channel_is_closed(sdata.channel) &&
|
||||
!ssh_channel_is_eof(sdata.channel);
|
||||
iter++) {
|
||||
if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event)
|
||||
ssh_event_free(event);
|
||||
if (session) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
}
|
||||
if (sshbind)
|
||||
ssh_bind_free(sshbind);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Public API - start mock SSH server */
|
||||
int ssh_mock_server_start(struct ssh_mock_server_config *config,
|
||||
pthread_t *thread)
|
||||
{
|
||||
if (!config || !thread)
|
||||
return -1;
|
||||
|
||||
config->server_ready = false;
|
||||
config->server_error = false;
|
||||
|
||||
if (pthread_create(thread, NULL, server_thread_func, config) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 50 && !config->server_ready && !config->server_error;
|
||||
i++) {
|
||||
usleep(100);
|
||||
}
|
||||
|
||||
return config->server_error ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Generic protocol callback - sends raw fuzzer data for any protocol */
|
||||
int ssh_mock_send_raw_data(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
|
||||
ssh_channel target_channel = (ssh_channel)channel;
|
||||
|
||||
/* Send raw fuzzer data - let protocol parser interpret it */
|
||||
if (size > 0) {
|
||||
ssh_channel_write(target_channel, data, size);
|
||||
}
|
||||
|
||||
/* Close channel to signal completion */
|
||||
ssh_channel_send_eof(target_channel);
|
||||
ssh_channel_close(target_channel);
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Write fixed ed25519 host key to file */
|
||||
int ssh_mock_write_hostkey(const char *path)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (fp == NULL)
|
||||
return -1;
|
||||
|
||||
size_t len = strlen(ssh_mock_ed25519_key_pem);
|
||||
size_t nwritten = fwrite(ssh_mock_ed25519_key_pem, 1, len, fp);
|
||||
fclose(fp);
|
||||
|
||||
return (nwritten == len) ? 0 : -1;
|
||||
}
|
||||
60
tests/fuzz/ssh_server_mock.h
Normal file
60
tests/fuzz/ssh_server_mock.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef SSH_SERVER_MOCK_H
|
||||
#define SSH_SERVER_MOCK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Server callback type */
|
||||
typedef int (*ssh_mock_callback_fn)(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata);
|
||||
|
||||
/* Mock server configuration */
|
||||
struct ssh_mock_server_config {
|
||||
const uint8_t *protocol_data;
|
||||
size_t protocol_data_size;
|
||||
ssh_mock_callback_fn exec_callback;
|
||||
ssh_mock_callback_fn subsystem_callback;
|
||||
void *callback_userdata;
|
||||
const char *cipher;
|
||||
const char *hmac;
|
||||
int server_socket;
|
||||
int client_socket;
|
||||
bool server_ready;
|
||||
bool server_error;
|
||||
};
|
||||
|
||||
/* Public API functions */
|
||||
int ssh_mock_server_start(struct ssh_mock_server_config *config,
|
||||
pthread_t *thread);
|
||||
int ssh_mock_send_raw_data(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata);
|
||||
int ssh_mock_write_hostkey(const char *path);
|
||||
|
||||
/* Fixed ed25519 key constant */
|
||||
extern const char *ssh_mock_ed25519_key_pem;
|
||||
|
||||
/* Centralized hostkey path used by all mock servers */
|
||||
#define SSH_MOCK_HOSTKEY_PATH "/tmp/libssh_mock_fuzz_key"
|
||||
|
||||
#endif
|
||||
@@ -143,9 +143,9 @@ extern LIBSSH_THREAD int ssh_log_level;
|
||||
"\tHostName nonuser-testhost.com\n" \
|
||||
"Match all\n" \
|
||||
"\tHostName all-matched.com\n" \
|
||||
/* Unsupported options */ \
|
||||
"Match originalhost example\n" \
|
||||
"\tHostName original-example.com\n" \
|
||||
"\tUser originaluser\n" \
|
||||
"Match localuser guest\n" \
|
||||
"\tHostName local-guest.com\n"
|
||||
|
||||
@@ -851,26 +851,40 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "all-matched.com");
|
||||
assert_string_equal(session->opts.originalhost, "unmatched");
|
||||
|
||||
/* Hostname example does simple hostname matching */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "example.com");
|
||||
assert_string_equal(session->opts.originalhost, "example");
|
||||
|
||||
/* We can match also both hosts from a comma separated list */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example1");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "exampleN");
|
||||
assert_string_equal(session->opts.originalhost, "example1");
|
||||
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example2");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "exampleN");
|
||||
assert_string_equal(session->opts.originalhost, "example2");
|
||||
|
||||
/* We can match by user */
|
||||
/* We can match by originalhost */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "example.com");
|
||||
assert_string_equal(session->opts.originalhost, "example");
|
||||
/* Match originalhost sets User */
|
||||
assert_string_equal(session->opts.username, "originaluser");
|
||||
|
||||
/* We can match by user - clear originalhost to isolate user match */
|
||||
torture_reset_config(session);
|
||||
SAFE_FREE(session->opts.originalhost);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, "guest");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "guest.com");
|
||||
@@ -881,6 +895,7 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "testhost.com");
|
||||
assert_string_equal(session->opts.originalhost, "testhost");
|
||||
|
||||
/* We can also negate conditions */
|
||||
torture_reset_config(session);
|
||||
@@ -888,9 +903,43 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "nonuser-testhost.com");
|
||||
assert_string_equal(session->opts.originalhost, "testhost");
|
||||
|
||||
/* In this part, we try various other config files and strings. */
|
||||
|
||||
/* Match host compares against resolved hostname */
|
||||
config = "Host ssh-host\n"
|
||||
"\tHostname 10.1.1.1\n"
|
||||
"Match host 10.1.1.*\n"
|
||||
"\tPort 2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
session->opts.port = 0;
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "10.1.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "ssh-host");
|
||||
assert_int_equal(session->opts.port, 2222);
|
||||
|
||||
/* Match host falls back to originalhost when host is NULL */
|
||||
config = "Match host my_alias\n"
|
||||
"\tHostName alias-matched.com\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
SAFE_FREE(session->opts.username);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "alias-matched.com");
|
||||
|
||||
/* Match final is not completely supported, but should do quite much the
|
||||
* same as "match all". The trailing "all" is not mandatory. */
|
||||
config = "Match final all\n"
|
||||
@@ -1018,7 +1067,7 @@ static void torture_config_match(void **state,
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "unmatched");
|
||||
|
||||
/* Missing argument to unsupported option originalhost */
|
||||
/* Missing argument to option originalhost */
|
||||
config = "Match originalhost\n"
|
||||
"\tHost originalhost.com\n";
|
||||
if (file != NULL) {
|
||||
@@ -1289,7 +1338,6 @@ static void torture_config_proxyjump(void **state,
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -W '[%h]:%p' 2620:52:0::fed");
|
||||
|
||||
|
||||
/* Multiple @ is allowed in second jump */
|
||||
config = "Host allowed-hostname\n"
|
||||
"\tProxyJump localhost,user@principal.com@jumpbox:22\n";
|
||||
@@ -1351,7 +1399,73 @@ static void torture_config_proxyjump(void **state,
|
||||
"jumpbox",
|
||||
"user@principal.com",
|
||||
"22");
|
||||
/* Non-RFC-1035 alias (underscore) — accepted with non-strict parse */
|
||||
config = "Host alias-jump\n"
|
||||
"\tProxyJump my_alias\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root,
|
||||
"my_alias",
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
/* Non-RFC-1035 alias in multi-hop second jump */
|
||||
config = "Host alias-multi\n"
|
||||
"\tProxyJump localhost,my_alias:2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root,
|
||||
"my_alias",
|
||||
NULL,
|
||||
"2222");
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root->next,
|
||||
"localhost",
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
/* Non-RFC-1035 alias — proxycommand based */
|
||||
torture_setenv("OPENSSH_PROXYJUMP", "1");
|
||||
|
||||
config = "Host alias-jump\n"
|
||||
"\tProxyJump my_alias\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -W '[%h]:%p' my_alias");
|
||||
|
||||
/* Non-RFC-1035 alias in multi-hop — proxycommand based */
|
||||
config = "Host alias-multi\n"
|
||||
"\tProxyJump localhost,my_alias:2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -J my_alias:2222 -W '[%h]:%p' localhost");
|
||||
|
||||
torture_unsetenv("OPENSSH_PROXYJUMP");
|
||||
|
||||
/* In this part, we try various other config files and strings. */
|
||||
torture_setenv("OPENSSH_PROXYJUMP", "1");
|
||||
@@ -2762,21 +2876,36 @@ static void torture_config_parse_uri(void **state)
|
||||
|
||||
(void)state; /* unused */
|
||||
|
||||
rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("localhost",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
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);
|
||||
rc = ssh_config_parse_uri("1.2.3.4",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
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);
|
||||
rc = ssh_config_parse_uri("1.2.3.4:2222",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1.2.3.4");
|
||||
@@ -2784,7 +2913,12 @@ static void torture_config_parse_uri(void **state)
|
||||
assert_string_equal(port, "2222");
|
||||
SAFE_FREE(port);
|
||||
|
||||
rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("[1:2:3::4]:2222",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1:2:3::4");
|
||||
@@ -2793,13 +2927,64 @@ static void torture_config_parse_uri(void **state)
|
||||
SAFE_FREE(port);
|
||||
|
||||
/* do not want port */
|
||||
rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true);
|
||||
rc = ssh_config_parse_uri("1:2:3::4",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
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);
|
||||
rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true, true);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict accepts non-RFC1035 chars (e.g. _, %) */
|
||||
rc = ssh_config_parse_uri("customer_1",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "customer_1");
|
||||
SAFE_FREE(hostname);
|
||||
|
||||
rc = ssh_config_parse_uri("admin@%prod",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_return_code(rc, errno);
|
||||
assert_string_equal(username, "admin");
|
||||
assert_string_equal(hostname, "%prod");
|
||||
SAFE_FREE(username);
|
||||
SAFE_FREE(hostname);
|
||||
|
||||
/* Strict rejects what non-strict accepts */
|
||||
rc = ssh_config_parse_uri("customer_1",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
true);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict rejects shell metacharacters */
|
||||
rc = ssh_config_parse_uri("host;cmd",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict rejects leading dash */
|
||||
rc = ssh_config_parse_uri("-host", &username, &hostname, NULL, true, false);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
@@ -2933,6 +3118,48 @@ static void torture_config_jump(void **state)
|
||||
printf("%s: EOF\n", __func__);
|
||||
}
|
||||
|
||||
/* Verify Hostname directive resolves host without overwriting originalhost
|
||||
*/
|
||||
static void torture_config_hostname(void **state)
|
||||
{
|
||||
ssh_session session = *state;
|
||||
char *expanded = NULL;
|
||||
|
||||
/* Hostname directive sets host, originalhost is unchanged */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
_parse_config(session,
|
||||
NULL,
|
||||
"Host my_alias\n\tHostname 192.168.1.1\n",
|
||||
SSH_OK);
|
||||
assert_string_equal(session->opts.host, "192.168.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
|
||||
/* Host keyword compares against originalhost, not the resolved IP */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
|
||||
_parse_config(session,
|
||||
NULL,
|
||||
"Host ssh-host\n\tHostname 10.1.1.1\n"
|
||||
"Host 10.1.1.*\n\tProxyJump ssh-host\n",
|
||||
SSH_OK);
|
||||
assert_string_equal(session->opts.host, "10.1.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "ssh-host");
|
||||
assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0);
|
||||
assert_null(session->opts.ProxyCommand);
|
||||
|
||||
/* %h falls back to originalhost when host is not yet resolved */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
expanded = ssh_path_expand_escape(session, "%h");
|
||||
assert_non_null(expanded);
|
||||
assert_string_equal(expanded, "my_alias");
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
/* Invalid configuration files
|
||||
*/
|
||||
static void torture_config_invalid(void **state)
|
||||
@@ -3101,7 +3328,8 @@ int torture_run_tests(void)
|
||||
cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value,
|
||||
setup,
|
||||
teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_jump,
|
||||
cmocka_unit_test_setup_teardown(torture_config_jump, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_hostname,
|
||||
setup,
|
||||
teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_invalid,
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
#include <libssh/pki.h>
|
||||
#include <libssh/pki_priv.h>
|
||||
#include <libssh/session.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <netioapi.h>
|
||||
#else
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SERVER
|
||||
#include <libssh/bind.h>
|
||||
#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config"
|
||||
@@ -59,12 +66,16 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "localhost");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "localhost");
|
||||
|
||||
/* IPv4 address */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1");
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "127.1.1.1");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "127.1.1.1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IPv6 address */
|
||||
@@ -72,12 +83,16 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "::1");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "::1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation");
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "meditation");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "meditation");
|
||||
assert_non_null(session->opts.username);
|
||||
assert_string_equal(session->opts.username, "guru");
|
||||
|
||||
@@ -86,6 +101,8 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "hostname");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "hostname");
|
||||
assert_non_null(session->opts.username);
|
||||
assert_string_equal(session->opts.username, "at@login");
|
||||
|
||||
@@ -104,6 +121,9 @@ static void torture_options_set_host(void **state) {
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IPv6 hostnames should work also with square braces */
|
||||
@@ -114,20 +134,103 @@ static void torture_options_set_host(void **state) {
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* user@IPv6%interface
|
||||
* Use dynamic interface name for cross-platform portability */
|
||||
{
|
||||
char interf[IF_NAMESIZE] = {0};
|
||||
char ipv6_zone[128] = {0};
|
||||
char expected_host[128] = {0};
|
||||
|
||||
if_indextoname(1, interf);
|
||||
assert_non_null(interf);
|
||||
snprintf(ipv6_zone, sizeof(ipv6_zone), "user@fe80::1%%%s", interf);
|
||||
snprintf(expected_host, sizeof(expected_host), "fe80::1%%%s", interf);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, ipv6_zone);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, expected_host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, expected_host);
|
||||
assert_string_equal(session->opts.username, "user");
|
||||
}
|
||||
|
||||
/* IDN need to be in punycode format */
|
||||
SAFE_FREE(session->opts.username);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld");
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "xn--bcher-kva.tld");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "xn--bcher-kva.tld");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IDN in UTF8 won't work */
|
||||
/* IDN in UTF-8 is accepted but not as a valid hostname,
|
||||
* only originalhost is set */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld");
|
||||
assert_string_equal(ssh_get_error(session),
|
||||
"Invalid argument in ssh_options_set");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "bücher.tld");
|
||||
|
||||
/* Config alias '%' rejected by RFC1035, only originalhost is set */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "%customer1");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "%customer1");
|
||||
|
||||
/* user@alias '_' rejected by RFC1035, alias stored in originalhost */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "admin@customer_1");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_string_equal(session->opts.username, "admin");
|
||||
|
||||
/* Shell metacharacters and leading dash rejected.
|
||||
* Verify failure cases do not update session options. */
|
||||
SAFE_FREE(session->opts.username);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "host;rm -rf /");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "-leading-dash");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* Empty user or host rejected */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "@hostname");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "user@");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
}
|
||||
|
||||
static void torture_options_set_ciphers(void **state)
|
||||
@@ -714,6 +817,26 @@ static void torture_options_get_host(void **state)
|
||||
|
||||
assert_string_equal(host, "localhost");
|
||||
ssh_string_free_char(host);
|
||||
|
||||
/* When host is not yet resolved, falls back to originalhost */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_true(rc == 0);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
|
||||
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
|
||||
assert_string_equal(host, "my_alias");
|
||||
ssh_string_free_char(host);
|
||||
|
||||
/* After config resolution, get returns resolved host, not originalhost */
|
||||
session->opts.host = strdup("192.168.1.1");
|
||||
assert_non_null(session->opts.host);
|
||||
|
||||
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
|
||||
assert_string_equal(host, "192.168.1.1");
|
||||
ssh_string_free_char(host);
|
||||
/* originalhost is unchanged */
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
}
|
||||
|
||||
static void torture_options_set_port(void **state)
|
||||
@@ -1074,6 +1197,7 @@ static void torture_options_config_host(void **state)
|
||||
{
|
||||
ssh_session session = *state;
|
||||
FILE *config = NULL;
|
||||
int rv;
|
||||
|
||||
/* create a new config file */
|
||||
config = fopen("test_config", "w");
|
||||
@@ -1113,6 +1237,33 @@ static void torture_options_config_host(void **state)
|
||||
ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(session->opts.port, 44);
|
||||
|
||||
/* ssh_options_parse_config rejects when originalhost is NULL */
|
||||
SAFE_FREE(session->opts.host);
|
||||
SAFE_FREE(session->opts.originalhost);
|
||||
rv = ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(rv, -1);
|
||||
|
||||
/* Config Hostname with invalid hostname: verify stale host not leaked */
|
||||
torture_write_file("test_config", "Host 192.168.1.1\nHostname my_alias\n");
|
||||
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "192.168.1.1");
|
||||
assert_string_equal(session->opts.host, "192.168.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "192.168.1.1");
|
||||
rv = ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(rv, 0);
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "192.168.1.1");
|
||||
|
||||
/* Calling ssh_options_set(HOST) twice: verify stale host not leaked */
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "real.server.com");
|
||||
assert_string_equal(session->opts.host, "real.server.com");
|
||||
assert_string_equal(session->opts.originalhost, "real.server.com");
|
||||
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
|
||||
unlink("test_config");
|
||||
}
|
||||
|
||||
@@ -1437,6 +1588,7 @@ static void torture_options_copy(void **state)
|
||||
|
||||
assert_string_equal(session->opts.username, new->opts.username);
|
||||
assert_string_equal(session->opts.host, new->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, new->opts.originalhost);
|
||||
assert_string_equal(session->opts.bindaddr, new->opts.bindaddr);
|
||||
assert_string_equal(session->opts.sshdir, new->opts.sshdir);
|
||||
assert_string_equal(session->opts.knownhosts, new->opts.knownhosts);
|
||||
|
||||
Reference in New Issue
Block a user