mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-02-06 18:29:50 +09:00
Compare commits
25 Commits
b2abcf8534
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc39902006 | ||
|
|
29dd7874cd | ||
|
|
8a134e03db | ||
|
|
39d931f7e5 | ||
|
|
b4f6d8b800 | ||
|
|
c78d2bb8fb | ||
|
|
5b0cee7c1b | ||
|
|
59ed66b684 | ||
|
|
ce0b616bc6 | ||
|
|
31ceec02fe | ||
|
|
a7cf4bb37b | ||
|
|
3dfaa70fcf | ||
|
|
76b14eaed7 | ||
|
|
8e8f091aba | ||
|
|
d75a54e206 | ||
|
|
efb7a7c4e0 | ||
|
|
7342e73d10 | ||
|
|
832f92e35f | ||
|
|
ea3464532e | ||
|
|
7e235f8748 | ||
|
|
052c8217b7 | ||
|
|
26b9ba5f8c | ||
|
|
1b3c061aae | ||
|
|
1525ea3dda | ||
|
|
a189c2ef4d |
@@ -171,21 +171,15 @@ The following RFC documents described SSH-2 protocol as an Internet standard.
|
||||
The Secure Shell (SSH) Session Channel Break Extension
|
||||
- <a href="https://tools.ietf.org/html/rfc4344" target="_blank">RFC 4344</a>,
|
||||
The Secure Shell (SSH) Transport Layer Encryption Modes
|
||||
- <a href="https://tools.ietf.org/html/rfc4345" target="_blank">RFC 4345</a>,
|
||||
Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol
|
||||
|
||||
It was later modified and expanded by the following RFCs.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/rfc4419" target="_blank">RFC 4419</a>,
|
||||
Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer
|
||||
Protocol
|
||||
- <a href="https://tools.ietf.org/html/rfc4432" target="_blank">RFC 4432</a>,
|
||||
RSA Key Exchange for the Secure Shell (SSH) Transport Layer Protocol
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc4462" target="_blank">RFC 4462</a>,
|
||||
Generic Security Service Application Program Interface (GSS-API)
|
||||
Authentication and Key Exchange for the Secure Shell (SSH) Protocol
|
||||
(only the authentication implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc4716" target="_blank">RFC 4716</a>,
|
||||
The Secure Shell (SSH) Public Key File Format
|
||||
(not implemented in libssh)
|
||||
@@ -204,7 +198,6 @@ It was later modified and expanded by the following RFCs.
|
||||
(not implemented in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc8160" target="_blank">RFC 8160</a>,
|
||||
IUTF8 Terminal Mode in Secure Shell (SSH)
|
||||
(not handled in libssh)
|
||||
- <a href="https://tools.ietf.org/html/rfc8270" target="_blank">RFC 8270</a>,
|
||||
Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits
|
||||
- <a href="https://tools.ietf.org/html/rfc8308" target="_blank">RFC 8308</a>,
|
||||
@@ -223,6 +216,14 @@ There are also drafts that are being currently developed and followed.
|
||||
|
||||
- <a href="https://tools.ietf.org/html/draft-miller-ssh-agent-03" target="_blank">draft-miller-ssh-agent-08</a>
|
||||
SSH Agent Protocol
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-sshm-mlkem-hybrid-kex-09" target="_blank">draft-ietf-sshm-mlkem-hybrid-kex-09</a>
|
||||
PQ/T Hybrid Key Exchange with ML-KEM in SSH
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-sshm-ntruprime-ssh-06" target="_blank">draft-ietf-sshm-ntruprime-ssh-06</a>
|
||||
Secure Shell (SSH) Key Exchange Method Using Hybrid Streamlined NTRU Prime sntrup761 and X25519 with SHA-512: sntrup761x25519-sha512
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-sshm-chacha20-poly1305-02" target="_blank">draft-ietf-sshm-chacha20-poly1305-02</a>
|
||||
Secure Shell (SSH) authenticated encryption cipher: chacha20-poly1305
|
||||
- <a href="https://tools.ietf.org/html/draft-ietf-sshm-strict-kex-01" target="_blank">draft-ietf-sshm-strict-kex-01</a>
|
||||
SSH Strict KEX extension
|
||||
|
||||
Interesting cryptography documents:
|
||||
|
||||
@@ -247,8 +248,6 @@ them like the statvfs calls in SFTP or the ssh-agent.
|
||||
OpenSSH's deviations and extensions</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.certkeys" target="_blank">
|
||||
OpenSSH's pubkey certificate authentication</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.chacha20poly1305" target="_blank">
|
||||
chacha20-poly1305@openssh.com authenticated encryption mode</a>
|
||||
- <a href="https://api.libssh.org/rfc/PROTOCOL.key" target="_blank">
|
||||
OpenSSH private key format (openssh-key-v1)</a>
|
||||
|
||||
|
||||
@@ -432,6 +432,7 @@ enum ssh_options_e {
|
||||
SSH_OPTIONS_ADDRESS_FAMILY,
|
||||
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE,
|
||||
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
|
||||
SSH_OPTIONS_NEXT_IDENTITY,
|
||||
};
|
||||
|
||||
enum {
|
||||
|
||||
@@ -43,8 +43,9 @@ extern "C" {
|
||||
|
||||
/* in misc.c */
|
||||
/* gets the user home dir. */
|
||||
char *ssh_get_user_home_dir(void);
|
||||
char *ssh_get_user_home_dir(ssh_session session);
|
||||
char *ssh_get_local_username(void);
|
||||
char *ssh_get_local_hostname(void);
|
||||
int ssh_file_readaccess_ok(const char *file);
|
||||
int ssh_dir_writeable(const char *path);
|
||||
|
||||
|
||||
@@ -246,13 +246,16 @@ struct ssh_session_struct {
|
||||
struct {
|
||||
struct ssh_list *identity;
|
||||
struct ssh_list *identity_non_exp;
|
||||
struct ssh_iterator *identity_it;
|
||||
struct ssh_list *certificate;
|
||||
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 */
|
||||
char *homedir;
|
||||
char *sshdir;
|
||||
char *knownhosts;
|
||||
char *global_knownhosts;
|
||||
|
||||
@@ -1396,6 +1396,11 @@ int ssh_userauth_publickey_auto(ssh_session session,
|
||||
if (session == NULL) {
|
||||
return SSH_AUTH_ERROR;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
"Starting authentication as a user %s",
|
||||
username ? username : session->opts.username);
|
||||
|
||||
if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) {
|
||||
session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY;
|
||||
return SSH_AUTH_DENIED;
|
||||
|
||||
15
src/config.c
15
src/config.c
@@ -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, ',');
|
||||
|
||||
111
src/connector.c
111
src/connector.c
@@ -305,6 +305,87 @@ static void ssh_connector_reset_pollevents(ssh_connector connector)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Update the connector's flags after a read-write io
|
||||
* operation
|
||||
*
|
||||
* This should be called after some data is successfully read from
|
||||
* connector's input and written to connector's output.
|
||||
*
|
||||
* @param[in, out] connector Connector for which the io operation occured.
|
||||
*
|
||||
* @warning This does not consider the case when the io indicated failure
|
||||
*
|
||||
* @warning This does not consider the case when the input indicated that
|
||||
* EOF was encountered.
|
||||
*/
|
||||
static void ssh_connector_update_flags_after_io(ssh_connector connector)
|
||||
{
|
||||
/*
|
||||
* With fds we can afford to mark:
|
||||
* - in_available as 0 after an fd read (even if more pending data can be
|
||||
* immediately read from the fd)
|
||||
*
|
||||
* - out_wontblock as 0 after an fd write (even if more data can
|
||||
* be written to the fd without blocking)
|
||||
*
|
||||
* since poll events set on the fd will get raised to indicate
|
||||
* possibility of read/write in case existing situation is apt
|
||||
* (i.e can read/write occur right now) or if situation becomes
|
||||
* apt in future (read data becomes available, write becomes
|
||||
* possible)
|
||||
*/
|
||||
|
||||
/*
|
||||
* On the other hand, with channels we need to be more careful
|
||||
* before claiming read/write not possible because channel callbacks
|
||||
* are called in limited scenarios.
|
||||
*
|
||||
* (e.g. connector callback to indicate read data available on input
|
||||
* channel is called only when new data is received on channel. It is
|
||||
* not called when we have some pending data in channel's buffers but
|
||||
* don't receive any new data on the channel)
|
||||
*
|
||||
* Hence, in case of channels, blindly setting flag associated with
|
||||
* read/write input/output to 0 after a read/write may not be a good
|
||||
* idea as the callback that sets it back to 1 again may not be ever
|
||||
* called again.
|
||||
*/
|
||||
|
||||
uint32_t window_size;
|
||||
|
||||
/* update in_available based on input source (fd or channel) */
|
||||
if (connector->in_fd != SSH_INVALID_SOCKET) {
|
||||
connector->in_available = 0;
|
||||
} else if (connector->in_channel != NULL) {
|
||||
if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0) {
|
||||
connector->in_available = 1;
|
||||
} else {
|
||||
connector->in_available = 0;
|
||||
}
|
||||
} else {
|
||||
/* connector input is invalid ! */
|
||||
return;
|
||||
}
|
||||
|
||||
/* update out_wontblock based on output source (fd or channel) */
|
||||
if (connector->out_fd != SSH_INVALID_SOCKET) {
|
||||
connector->out_wontblock = 0;
|
||||
} else if (connector->out_channel != NULL) {
|
||||
window_size = ssh_channel_window_size(connector->out_channel);
|
||||
if (window_size > 0) {
|
||||
connector->out_wontblock = 1;
|
||||
} else {
|
||||
connector->out_wontblock = 0;
|
||||
}
|
||||
} else {
|
||||
/* connector output is invalid ! */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
@@ -390,8 +471,8 @@ static void ssh_connector_fd_in_cb(ssh_connector connector)
|
||||
ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed");
|
||||
return;
|
||||
}
|
||||
connector->out_wontblock = 0;
|
||||
connector->in_available = 0;
|
||||
|
||||
ssh_connector_update_flags_after_io(connector);
|
||||
} else {
|
||||
connector->in_available = 1;
|
||||
}
|
||||
@@ -444,8 +525,8 @@ ssh_connector_fd_out_cb(ssh_connector connector)
|
||||
"Output socket or channel closed");
|
||||
return;
|
||||
}
|
||||
connector->in_available = 0;
|
||||
connector->out_wontblock = 0;
|
||||
|
||||
ssh_connector_update_flags_after_io(connector);
|
||||
} else {
|
||||
connector->out_wontblock = 1;
|
||||
}
|
||||
@@ -542,16 +623,8 @@ static int ssh_connector_channel_data_cb(ssh_session session,
|
||||
window_len = MIN(window, len);
|
||||
|
||||
/* Route the data to the right exception channel */
|
||||
if (is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR)) {
|
||||
w = ssh_channel_write_stderr(connector->out_channel,
|
||||
data,
|
||||
window_len);
|
||||
} else if (!is_stderr &&
|
||||
(connector->out_flags & SSH_CONNECTOR_STDOUT)) {
|
||||
w = ssh_channel_write(connector->out_channel,
|
||||
data,
|
||||
window_len);
|
||||
} else if (connector->out_flags & SSH_CONNECTOR_STDOUT) {
|
||||
if (connector->out_flags & SSH_CONNECTOR_STDOUT &&
|
||||
!(is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR))) {
|
||||
w = ssh_channel_write(connector->out_channel,
|
||||
data,
|
||||
window_len);
|
||||
@@ -574,11 +647,7 @@ static int ssh_connector_channel_data_cb(ssh_session session,
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
connector->out_wontblock = 0;
|
||||
connector->in_available = 0;
|
||||
if ((unsigned int)w < len) {
|
||||
connector->in_available = 1;
|
||||
}
|
||||
ssh_connector_update_flags_after_io(connector);
|
||||
ssh_connector_reset_pollevents(connector);
|
||||
|
||||
return w;
|
||||
@@ -650,8 +719,8 @@ ssh_connector_channel_write_wontblock_cb(ssh_session session,
|
||||
|
||||
return 0;
|
||||
}
|
||||
connector->in_available = 0;
|
||||
connector->out_wontblock = 0;
|
||||
|
||||
ssh_connector_update_flags_after_io(connector);
|
||||
} else {
|
||||
connector->out_wontblock = 1;
|
||||
}
|
||||
|
||||
23
src/gssapi.c
23
src/gssapi.c
@@ -198,7 +198,7 @@ int
|
||||
ssh_gssapi_handle_userauth(ssh_session session, const char *user,
|
||||
uint32_t n_oid, ssh_string *oids)
|
||||
{
|
||||
char hostname[NI_MAXHOST] = {0};
|
||||
char *hostname = NULL;
|
||||
OM_uint32 maj_stat, min_stat;
|
||||
size_t i;
|
||||
gss_OID_set supported; /* oids supported by server */
|
||||
@@ -210,14 +210,6 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
|
||||
int rc;
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
|
||||
rc = gethostname(hostname, 64);
|
||||
if (rc != 0) {
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Error getting hostname: %s",
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Destroy earlier GSSAPI context if any */
|
||||
ssh_gssapi_free(session);
|
||||
rc = ssh_gssapi_init(session);
|
||||
@@ -284,7 +276,16 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
hostname = ssh_get_local_hostname();
|
||||
if (hostname == NULL) {
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Error getting hostname: %s",
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = ssh_gssapi_import_name(session->gssapi, hostname);
|
||||
SAFE_FREE(hostname);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_auth_reply_default(session, 0);
|
||||
gss_release_oid_set(&min_stat, &both_supported);
|
||||
@@ -850,6 +851,10 @@ int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids)
|
||||
char *ptr = NULL;
|
||||
int ret;
|
||||
|
||||
if (session == NULL || session->gssapi == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (session->gssapi->client.client_deleg_creds == NULL) {
|
||||
if (session->opts.gss_client_identity != NULL) {
|
||||
namebuf.value = (void *)session->opts.gss_client_identity;
|
||||
|
||||
@@ -421,7 +421,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
gss_name_t client_name = GSS_C_NO_NAME;
|
||||
OM_uint32 ret_flags = 0;
|
||||
gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER;
|
||||
char hostname[NI_MAXHOST] = {0};
|
||||
char *hostname = NULL;
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
|
||||
rc = ssh_buffer_unpack(packet, "S", &otoken);
|
||||
@@ -538,8 +538,8 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = gethostname(hostname, 64);
|
||||
if (rc != 0) {
|
||||
hostname = ssh_get_local_hostname();
|
||||
if (hostname == NULL) {
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Error getting hostname: %s",
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
@@ -547,6 +547,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
}
|
||||
|
||||
rc = ssh_gssapi_import_name(session->gssapi, hostname);
|
||||
SAFE_FREE(hostname);
|
||||
if (rc != SSH_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -216,11 +216,22 @@ ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This method reads the known_hosts file referenced by the path
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Read entries from filename to provided list
|
||||
*
|
||||
* This method reads the known_hosts file referenced by the path
|
||||
* in filename argument, and entries matching the match argument
|
||||
* will be added to the list in entries argument.
|
||||
* If the entries list is NULL, it will allocate a new list. Caller
|
||||
* is responsible to free it even if an error occurs.
|
||||
*
|
||||
* @param match[in] The host name (with port) to match against
|
||||
* @param filename[in] The known hosts file to parse
|
||||
* @param entries[in,out] The list of entries to append matching ones
|
||||
* @return `SSH_OK` on missing file or success parsing,
|
||||
* `SSH_ERROR` on error
|
||||
*/
|
||||
static int ssh_known_hosts_read_entries(const char *match,
|
||||
const char *filename,
|
||||
@@ -346,6 +357,33 @@ static char *ssh_session_get_host_port(ssh_session session)
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Free known hosts entries list
|
||||
*
|
||||
* @param[in] entry_list The list of ssh_knownhosts_entry items
|
||||
*/
|
||||
static void ssh_knownhosts_entries_free(struct ssh_list *entry_list)
|
||||
{
|
||||
struct ssh_iterator *it = NULL;
|
||||
|
||||
if (entry_list == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (it = ssh_list_get_iterator(entry_list);
|
||||
it != NULL;
|
||||
it = ssh_list_get_iterator(entry_list)) {
|
||||
struct ssh_knownhosts_entry *entry = NULL;
|
||||
|
||||
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
|
||||
ssh_knownhosts_entry_free(entry);
|
||||
ssh_list_remove(entry_list, it);
|
||||
}
|
||||
ssh_list_free(entry_list);
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Check which host keys should be preferred for the session.
|
||||
*
|
||||
* This checks the known_hosts file to find out which algorithms should be
|
||||
@@ -453,7 +491,7 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session)
|
||||
|
||||
return list;
|
||||
error:
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
ssh_list_free(list);
|
||||
return NULL;
|
||||
}
|
||||
@@ -505,6 +543,7 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Get the host keys algorithms identifiers from the known_hosts files
|
||||
*
|
||||
* This expands the signatures types that can be generated from the keys types
|
||||
@@ -549,7 +588,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session)
|
||||
&entry_list);
|
||||
if (rc != 0) {
|
||||
SAFE_FREE(host_port);
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -558,7 +597,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session)
|
||||
&entry_list);
|
||||
SAFE_FREE(host_port);
|
||||
if (rc != 0) {
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -799,7 +838,6 @@ out:
|
||||
enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
|
||||
{
|
||||
struct ssh_list *entry_list = NULL;
|
||||
struct ssh_iterator *it = NULL;
|
||||
char *host_port = NULL;
|
||||
bool global_known_hosts_found = false;
|
||||
bool known_hosts_found = false;
|
||||
@@ -860,7 +898,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
|
||||
&entry_list);
|
||||
if (rc != 0) {
|
||||
SAFE_FREE(host_port);
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return SSH_KNOWN_HOSTS_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -871,7 +909,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
|
||||
&entry_list);
|
||||
if (rc != 0) {
|
||||
SAFE_FREE(host_port);
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return SSH_KNOWN_HOSTS_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -883,16 +921,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
|
||||
return SSH_KNOWN_HOSTS_UNKNOWN;
|
||||
}
|
||||
|
||||
for (it = ssh_list_get_iterator(entry_list);
|
||||
it != NULL;
|
||||
it = ssh_list_get_iterator(entry_list)) {
|
||||
struct ssh_knownhosts_entry *entry = NULL;
|
||||
|
||||
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
|
||||
ssh_knownhosts_entry_free(entry);
|
||||
ssh_list_remove(entry_list, it);
|
||||
}
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
|
||||
return SSH_KNOWN_HOSTS_OK;
|
||||
}
|
||||
@@ -1079,13 +1108,13 @@ ssh_known_hosts_check_server_key(const char *hosts_entry,
|
||||
filename,
|
||||
&entry_list);
|
||||
if (rc != 0) {
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return SSH_KNOWN_HOSTS_UNKNOWN;
|
||||
}
|
||||
|
||||
it = ssh_list_get_iterator(entry_list);
|
||||
if (it == NULL) {
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
return SSH_KNOWN_HOSTS_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -1115,16 +1144,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry,
|
||||
}
|
||||
}
|
||||
|
||||
for (it = ssh_list_get_iterator(entry_list);
|
||||
it != NULL;
|
||||
it = ssh_list_get_iterator(entry_list)) {
|
||||
struct ssh_knownhosts_entry *entry = NULL;
|
||||
|
||||
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
|
||||
ssh_knownhosts_entry_free(entry);
|
||||
ssh_list_remove(entry_list, it);
|
||||
}
|
||||
ssh_list_free(entry_list);
|
||||
ssh_knownhosts_entries_free(entry_list);
|
||||
|
||||
return found;
|
||||
}
|
||||
@@ -1196,6 +1216,8 @@ ssh_session_get_known_hosts_entry(ssh_session session,
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Get the known_hosts entry for the current connected session
|
||||
* from the given known_hosts file.
|
||||
*
|
||||
|
||||
@@ -615,10 +615,10 @@ int ssh_publickey_to_file(ssh_session session,
|
||||
FILE *fp = NULL;
|
||||
char *user = NULL;
|
||||
char buffer[1024];
|
||||
char host[256];
|
||||
char *host = NULL;
|
||||
unsigned char *pubkey_64 = NULL;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
if(session==NULL)
|
||||
return SSH_ERROR;
|
||||
if(file==NULL || pubkey==NULL){
|
||||
@@ -636,8 +636,8 @@ int ssh_publickey_to_file(ssh_session session,
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = gethostname(host, sizeof(host));
|
||||
if (rc < 0) {
|
||||
host = ssh_get_local_hostname();
|
||||
if (host == NULL) {
|
||||
SAFE_FREE(user);
|
||||
SAFE_FREE(pubkey_64);
|
||||
return SSH_ERROR;
|
||||
@@ -651,6 +651,7 @@ int ssh_publickey_to_file(ssh_session session,
|
||||
|
||||
SAFE_FREE(pubkey_64);
|
||||
SAFE_FREE(user);
|
||||
SAFE_FREE(host);
|
||||
|
||||
SSH_LOG(SSH_LOG_RARE, "Trying to write public key file: %s", file);
|
||||
SSH_LOG(SSH_LOG_PACKET, "public key file content: %s", buffer);
|
||||
|
||||
339
src/misc.c
339
src/misc.c
@@ -108,22 +108,22 @@
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
char *ssh_get_user_home_dir(void)
|
||||
static char *ssh_get_user_home_dir_internal(void)
|
||||
{
|
||||
char tmp[PATH_MAX] = {0};
|
||||
char *szPath = NULL;
|
||||
char tmp[PATH_MAX] = {0};
|
||||
char *szPath = NULL;
|
||||
|
||||
if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) {
|
||||
szPath = malloc(strlen(tmp) + 1);
|
||||
if (szPath == NULL) {
|
||||
return NULL;
|
||||
if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) {
|
||||
szPath = malloc(strlen(tmp) + 1);
|
||||
if (szPath == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strcpy(szPath, tmp);
|
||||
return szPath;
|
||||
}
|
||||
|
||||
strcpy(szPath, tmp);
|
||||
return szPath;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we have read access on file */
|
||||
@@ -298,7 +298,7 @@ int ssh_is_ipaddr(const char *str)
|
||||
#define NSS_BUFLEN_PASSWD 4096
|
||||
#endif /* NSS_BUFLEN_PASSWD */
|
||||
|
||||
char *ssh_get_user_home_dir(void)
|
||||
static char *ssh_get_user_home_dir_internal(void)
|
||||
{
|
||||
char *szPath = NULL;
|
||||
struct passwd pwd;
|
||||
@@ -313,7 +313,6 @@ char *ssh_get_user_home_dir(void)
|
||||
return NULL;
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%s", szPath);
|
||||
|
||||
return strdup(buf);
|
||||
}
|
||||
|
||||
@@ -428,6 +427,29 @@ int ssh_is_ipaddr(const char *str)
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
char *ssh_get_user_home_dir(ssh_session session)
|
||||
{
|
||||
char *szPath = NULL;
|
||||
|
||||
/* If used previously, reuse cached value */
|
||||
if (session != NULL && session->opts.homedir != NULL) {
|
||||
return strdup(session->opts.homedir);
|
||||
}
|
||||
|
||||
szPath = ssh_get_user_home_dir_internal();
|
||||
if (szPath == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (session != NULL) {
|
||||
/* cache it:
|
||||
* failure is not fatal -- at worst we will just not cache it */
|
||||
session->opts.homedir = strdup(szPath);
|
||||
}
|
||||
|
||||
return szPath;
|
||||
}
|
||||
|
||||
char *ssh_lowercase(const char* str)
|
||||
{
|
||||
char *new = NULL, *p = NULL;
|
||||
@@ -468,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.
|
||||
@@ -483,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -814,7 +847,17 @@ static struct ssh_iterator *ssh_iterator_new(const void *data)
|
||||
return iterator;
|
||||
}
|
||||
|
||||
int ssh_list_append(struct ssh_list *list,const void *data)
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Appends an element to the end of the list.
|
||||
*
|
||||
* @param[in] list The list to append the element
|
||||
* @param[in] data The element to append
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on error
|
||||
*/
|
||||
int ssh_list_append(struct ssh_list *list, const void *data)
|
||||
{
|
||||
struct ssh_iterator *iterator = NULL;
|
||||
|
||||
@@ -1179,7 +1222,7 @@ char *ssh_path_expand_tilde(const char *d)
|
||||
} else {
|
||||
ld = strlen(d);
|
||||
p = (char *) d;
|
||||
h = ssh_get_user_home_dir();
|
||||
h = ssh_get_user_home_dir(NULL);
|
||||
}
|
||||
if (h == NULL) {
|
||||
return NULL;
|
||||
@@ -1201,15 +1244,105 @@ char *ssh_path_expand_tilde(const char *d)
|
||||
return r;
|
||||
}
|
||||
|
||||
char *ssh_get_local_hostname(void)
|
||||
{
|
||||
char host[NI_MAXHOST] = {0};
|
||||
int rc;
|
||||
|
||||
rc = gethostname(host, sizeof(host));
|
||||
if (rc != 0) {
|
||||
return NULL;
|
||||
}
|
||||
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 SSH configuration directory (~/.ssh)
|
||||
* %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().
|
||||
*
|
||||
@@ -1217,7 +1350,6 @@ char *ssh_path_expand_tilde(const char *d)
|
||||
*/
|
||||
char *ssh_path_expand_escape(ssh_session session, const char *s)
|
||||
{
|
||||
char host[NI_MAXHOST] = {0};
|
||||
char *buf = NULL;
|
||||
char *r = NULL;
|
||||
char *x = NULL;
|
||||
@@ -1266,65 +1398,67 @@ char *ssh_path_expand_escape(ssh_session session, const char *s)
|
||||
}
|
||||
|
||||
switch (*p) {
|
||||
case '%':
|
||||
goto escape;
|
||||
case 'd':
|
||||
if (session->opts.sshdir) {
|
||||
x = strdup(session->opts.sshdir);
|
||||
} else {
|
||||
ssh_set_error(session, SSH_FATAL,
|
||||
"Cannot expand sshdir");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
x = ssh_get_local_username();
|
||||
break;
|
||||
case 'l':
|
||||
if (gethostname(host, sizeof(host) == 0)) {
|
||||
x = strdup(host);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
if (session->opts.host) {
|
||||
x = strdup(session->opts.host);
|
||||
} else {
|
||||
ssh_set_error(session, SSH_FATAL,
|
||||
"Cannot expand host");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (session->opts.username) {
|
||||
x = strdup(session->opts.username);
|
||||
} else {
|
||||
ssh_set_error(session, SSH_FATAL,
|
||||
"Cannot expand username");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
{
|
||||
char tmp[6];
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%hu",
|
||||
(uint16_t)(session->opts.port > 0 ? session->opts.port
|
||||
: 22));
|
||||
x = strdup(tmp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ssh_set_error(session, SSH_FATAL,
|
||||
"Wrong escape sequence detected");
|
||||
case '%':
|
||||
goto escape;
|
||||
case 'd':
|
||||
x = ssh_get_user_home_dir(session);
|
||||
if (x == NULL) {
|
||||
ssh_set_error(session, SSH_FATAL, "Cannot expand homedir");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
x = ssh_get_local_username();
|
||||
break;
|
||||
case 'l':
|
||||
x = ssh_get_local_hostname();
|
||||
break;
|
||||
case 'h':
|
||||
if (session->opts.host) {
|
||||
x = strdup(session->opts.host);
|
||||
} else {
|
||||
ssh_set_error(session, SSH_FATAL, "Cannot expand host");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (session->opts.username) {
|
||||
x = strdup(session->opts.username);
|
||||
} else {
|
||||
ssh_set_error(session, SSH_FATAL, "Cannot expand username");
|
||||
free(buf);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'p': {
|
||||
char tmp[6];
|
||||
unsigned int port;
|
||||
|
||||
ssh_options_get_port(session, &port);
|
||||
snprintf(tmp, sizeof(tmp), "%u", port);
|
||||
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);
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x == NULL) {
|
||||
@@ -1336,8 +1470,7 @@ char *ssh_path_expand_escape(ssh_session session, const char *s)
|
||||
|
||||
i += strlen(x);
|
||||
if (i >= MAX_BUF_SIZE) {
|
||||
ssh_set_error(session, SSH_FATAL,
|
||||
"String too long");
|
||||
ssh_set_error(session, SSH_FATAL, "String too long");
|
||||
free(buf);
|
||||
free(x);
|
||||
free(r);
|
||||
|
||||
@@ -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;
|
||||
@@ -1562,7 +1561,16 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) {
|
||||
* - SSH_OPTIONS_IDENTITY:
|
||||
* Get the first identity file name (const char *).\n
|
||||
* \n
|
||||
* By default id_rsa, id_ecdsa and id_ed25519 files are used.
|
||||
* By default `id_rsa`, `id_ecdsa`, `id_ed25519`, `id_ecdsa_sk`
|
||||
* and `id_ed25519_sk` (when SK support is built in) files are
|
||||
* used.
|
||||
*
|
||||
* - SSH_OPTIONS_NEXT_IDENTITY:
|
||||
* Get the next identity file name (const char *).\n
|
||||
* \n
|
||||
* Repeat calls to get all key paths. SSH_EOF is returned when
|
||||
* the end of list is reached. Another call will start another
|
||||
* iteration over the same list.
|
||||
*
|
||||
* - SSH_OPTIONS_PROXYCOMMAND:
|
||||
* Get the proxycommand necessary to log into the
|
||||
@@ -1658,6 +1666,30 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
|
||||
break;
|
||||
}
|
||||
|
||||
case SSH_OPTIONS_NEXT_IDENTITY: {
|
||||
if (session->opts.identity_it != NULL) {
|
||||
/* Move to the next item */
|
||||
session->opts.identity_it = session->opts.identity_it->next;
|
||||
if (session->opts.identity_it == NULL) {
|
||||
*value = NULL;
|
||||
return SSH_EOF;
|
||||
}
|
||||
} else {
|
||||
/* Get iterator from opts */
|
||||
struct ssh_iterator *it = NULL;
|
||||
it = ssh_list_get_iterator(session->opts.identity);
|
||||
if (it == NULL) {
|
||||
it = ssh_list_get_iterator(session->opts.identity_non_exp);
|
||||
}
|
||||
if (it == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
session->opts.identity_it = it;
|
||||
}
|
||||
src = ssh_iterator_value(char *, session->opts.identity_it);
|
||||
break;
|
||||
}
|
||||
|
||||
case SSH_OPTIONS_PROXYCOMMAND:
|
||||
src = session->opts.ProxyCommand;
|
||||
break;
|
||||
@@ -1963,7 +1995,7 @@ int ssh_options_parse_config(ssh_session session, const char *filename)
|
||||
|
||||
/* set default filename */
|
||||
if (filename == NULL) {
|
||||
expanded_filename = ssh_path_expand_escape(session, "%d/config");
|
||||
expanded_filename = ssh_path_expand_escape(session, "%d/.ssh/config");
|
||||
} else {
|
||||
expanded_filename = ssh_path_expand_escape(session, filename);
|
||||
}
|
||||
@@ -2021,7 +2053,7 @@ int ssh_options_apply(ssh_session session)
|
||||
|
||||
if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) {
|
||||
if (session->opts.knownhosts == NULL) {
|
||||
tmp = ssh_path_expand_escape(session, "%d/known_hosts");
|
||||
tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts");
|
||||
} else {
|
||||
tmp = ssh_path_expand_escape(session, session->opts.knownhosts);
|
||||
}
|
||||
|
||||
@@ -2684,7 +2684,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key,
|
||||
const char *filename)
|
||||
{
|
||||
char key_buf[MAX_LINE_SIZE];
|
||||
char host[256];
|
||||
char *host = NULL;
|
||||
char *b64_key = NULL;
|
||||
char *user = NULL;
|
||||
FILE *fp = NULL;
|
||||
@@ -2699,8 +2699,8 @@ int ssh_pki_export_pubkey_file(const ssh_key key,
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = gethostname(host, sizeof(host));
|
||||
if (rc < 0) {
|
||||
host = ssh_get_local_hostname();
|
||||
if (host == NULL) {
|
||||
free(user);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
@@ -2708,6 +2708,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key,
|
||||
rc = ssh_pki_export_pubkey_base64(key, &b64_key);
|
||||
if (rc < 0) {
|
||||
free(user);
|
||||
free(host);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
@@ -2718,6 +2719,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key,
|
||||
user,
|
||||
host);
|
||||
free(user);
|
||||
free(host);
|
||||
free(b64_key);
|
||||
if (rc < 0) {
|
||||
return SSH_ERROR;
|
||||
|
||||
@@ -168,7 +168,7 @@ ssh_session ssh_new(void)
|
||||
}
|
||||
#endif /* WITH_GSSAPI */
|
||||
|
||||
id = strdup("%d/id_ed25519");
|
||||
id = strdup("%d/.ssh/id_ed25519");
|
||||
if (id == NULL) {
|
||||
goto err;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ ssh_session ssh_new(void)
|
||||
}
|
||||
|
||||
#ifdef HAVE_ECC
|
||||
id = strdup("%d/id_ecdsa");
|
||||
id = strdup("%d/.ssh/id_ecdsa");
|
||||
if (id == NULL) {
|
||||
goto err;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ ssh_session ssh_new(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
id = strdup("%d/id_rsa");
|
||||
id = strdup("%d/.ssh/id_rsa");
|
||||
if (id == NULL) {
|
||||
goto err;
|
||||
}
|
||||
@@ -200,7 +200,7 @@ ssh_session ssh_new(void)
|
||||
|
||||
#ifdef WITH_FIDO2
|
||||
/* Add security key identities */
|
||||
id = strdup("%d/id_ed25519_sk");
|
||||
id = strdup("%d/.ssh/id_ed25519_sk");
|
||||
if (id == NULL) {
|
||||
goto err;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ ssh_session ssh_new(void)
|
||||
}
|
||||
|
||||
#ifdef HAVE_ECC
|
||||
id = strdup("%d/id_ecdsa_sk");
|
||||
id = strdup("%d/.ssh/id_ecdsa_sk");
|
||||
if (id == NULL) {
|
||||
goto err;
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -405,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.homedir);
|
||||
SAFE_FREE(session->opts.sshdir);
|
||||
SAFE_FREE(session->opts.knownhosts);
|
||||
SAFE_FREE(session->opts.global_knownhosts);
|
||||
|
||||
@@ -549,17 +549,14 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp,
|
||||
if (rc != SSH_OK){
|
||||
goto error;
|
||||
}
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Flags: %.8" PRIx32 "\n", attr->flags);
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Flags: %.8" PRIx32, attr->flags);
|
||||
|
||||
if (attr->flags & SSH_FILEXFER_ATTR_SIZE) {
|
||||
rc = ssh_buffer_unpack(buf, "q", &attr->size);
|
||||
if(rc != SSH_OK) {
|
||||
goto error;
|
||||
}
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Size: %" PRIu64 "\n",
|
||||
(uint64_t) attr->size);
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Size: %" PRIu64, (uint64_t)attr->size);
|
||||
}
|
||||
|
||||
if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) {
|
||||
|
||||
218
src/socket.c
218
src/socket.c
@@ -97,6 +97,10 @@ struct ssh_socket_struct {
|
||||
struct jump_thread_data_struct {
|
||||
ssh_session session;
|
||||
socket_t fd;
|
||||
char *next_hostname;
|
||||
uint16_t next_port;
|
||||
struct ssh_jump_info_struct *next_jump;
|
||||
struct ssh_jump_callbacks_struct *next_cb;
|
||||
};
|
||||
|
||||
int proxy_disconnect = 0;
|
||||
@@ -1249,6 +1253,22 @@ verify_knownhost(ssh_session session)
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
static void free_jump_thread_data(struct jump_thread_data_struct *data)
|
||||
{
|
||||
if (data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ssh_free(data->session);
|
||||
SAFE_FREE(data->next_hostname);
|
||||
if (data->next_jump != NULL) {
|
||||
SAFE_FREE(data->next_jump->hostname);
|
||||
SAFE_FREE(data->next_jump->username);
|
||||
}
|
||||
SAFE_FREE(data->next_jump);
|
||||
SAFE_FREE(data);
|
||||
}
|
||||
|
||||
static void *
|
||||
jump_thread_func(void *arg)
|
||||
{
|
||||
@@ -1260,72 +1280,30 @@ jump_thread_func(void *arg)
|
||||
int rc;
|
||||
ssh_event event = NULL;
|
||||
ssh_connector connector_in = NULL, connector_out = NULL;
|
||||
ssh_session session = NULL;
|
||||
int next_port;
|
||||
uint16_t next_port;
|
||||
char *next_hostname = NULL;
|
||||
|
||||
jump_thread_data = (struct jump_thread_data_struct *)arg;
|
||||
session = jump_thread_data->session;
|
||||
jump_session = jump_thread_data->session;
|
||||
|
||||
next_port = session->opts.port;
|
||||
next_hostname = strdup(session->opts.host);
|
||||
/* First thing we need to do is to set the right level as its kept in
|
||||
* thread local variable, therefore reset to 0 after spawning new thread.
|
||||
*/
|
||||
ssh_set_log_level(jump_session->common.log_verbosity);
|
||||
|
||||
jump_session = ssh_new();
|
||||
if (jump_session == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
cb = jump_thread_data->next_cb;
|
||||
jis = jump_thread_data->next_jump;
|
||||
|
||||
jump_session->proxy_root = false;
|
||||
/* Reset the global variable if it was previously 1 */
|
||||
if (session->proxy_root) {
|
||||
proxy_disconnect = 0;
|
||||
}
|
||||
|
||||
for (jis = ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
session->opts.proxy_jumps);
|
||||
jis != NULL;
|
||||
jis = ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
session->opts.proxy_jumps)) {
|
||||
rc = ssh_list_append(jump_session->opts.proxy_jumps, jis);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error_oom(session);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
for (jis =
|
||||
ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
session->opts.proxy_jumps_user_cb);
|
||||
jis != NULL;
|
||||
jis = ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
session->opts.proxy_jumps_user_cb)) {
|
||||
rc = ssh_list_append(jump_session->opts.proxy_jumps_user_cb, jis);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error_oom(session);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
ssh_options_set(jump_session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&session->common.log_verbosity);
|
||||
|
||||
/* Pop the information about the current jump */
|
||||
jis = ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
jump_session->opts.proxy_jumps);
|
||||
if (jis == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received");
|
||||
goto exit;
|
||||
}
|
||||
/* This is the calling thread target where we will eventually initialize
|
||||
* forwarding */
|
||||
next_port = jump_thread_data->next_port;
|
||||
next_hostname = jump_thread_data->next_hostname;
|
||||
|
||||
ssh_options_set(jump_session, SSH_OPTIONS_HOST, jis->hostname);
|
||||
ssh_options_set(jump_session, SSH_OPTIONS_USER, jis->username);
|
||||
ssh_options_set(jump_session, SSH_OPTIONS_PORT, &jis->port);
|
||||
|
||||
/* Pop the callbacks for the current jump */
|
||||
cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *,
|
||||
jump_session->opts.proxy_jumps_user_cb);
|
||||
|
||||
if (cb != NULL) {
|
||||
if (cb != NULL && cb->before_connection != NULL) {
|
||||
rc = cb->before_connection(jump_session, cb->userdata);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session));
|
||||
@@ -1333,6 +1311,13 @@ jump_thread_func(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PACKET,
|
||||
"Proxy connecting to host %s port %d user %s, callbacks=%p",
|
||||
jis->hostname,
|
||||
jis->port,
|
||||
jis->username,
|
||||
(void *)cb);
|
||||
|
||||
/* If there are more jumps then this will make a new thread and call the
|
||||
* current function again, until there are no jumps. When there are no jumps
|
||||
* it connects normally. */
|
||||
@@ -1423,36 +1408,42 @@ exit:
|
||||
ssh_event_remove_connector(event, connector_out);
|
||||
ssh_connector_free(connector_out);
|
||||
}
|
||||
SAFE_FREE(next_hostname);
|
||||
if (jis != NULL) {
|
||||
SAFE_FREE(jis->hostname);
|
||||
SAFE_FREE(jis->username);
|
||||
}
|
||||
SAFE_FREE(jis);
|
||||
|
||||
ssh_disconnect(jump_session);
|
||||
ssh_event_free(event);
|
||||
ssh_free(jump_session);
|
||||
|
||||
shutdown(jump_thread_data->fd, SHUT_RDWR);
|
||||
close(jump_thread_data->fd);
|
||||
SAFE_FREE(jump_thread_data);
|
||||
|
||||
free_jump_thread_data(jump_thread_data);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
int
|
||||
ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
{
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
ssh_poll_handle h = NULL;
|
||||
int rc;
|
||||
pthread_t jump_thread;
|
||||
struct ssh_jump_info_struct *jis = NULL;
|
||||
struct ssh_jump_callbacks_struct *cb = NULL;
|
||||
struct jump_thread_data_struct *jump_thread_data = NULL;
|
||||
socket_t pair[2];
|
||||
ssh_session jump_session = NULL, session = NULL;
|
||||
struct ssh_list *empty_list = NULL;
|
||||
socket_t pair[2] = {SSH_INVALID_SOCKET, SSH_INVALID_SOCKET};
|
||||
|
||||
session = s->session;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
"Connecting to host %s port %d user %s through ProxyJump",
|
||||
session->opts.host,
|
||||
session->opts.port,
|
||||
session->opts.username);
|
||||
|
||||
if (s->state != SSH_SOCKET_NONE) {
|
||||
ssh_set_error(
|
||||
s->session,
|
||||
session,
|
||||
SSH_FATAL,
|
||||
"ssh_socket_connect_proxyjump called on socket not unconnected");
|
||||
return SSH_ERROR;
|
||||
@@ -1460,50 +1451,100 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
|
||||
jump_thread_data = calloc(1, sizeof(struct jump_thread_data_struct));
|
||||
if (jump_thread_data == NULL) {
|
||||
ssh_set_error_oom(s->session);
|
||||
ssh_set_error_oom(session);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair);
|
||||
if (rc == -1) {
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
|
||||
ssh_set_error(s->session,
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Creating socket pair failed: %s",
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
SAFE_FREE(jump_thread_data);
|
||||
return SSH_ERROR;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
jump_thread_data->session = s->session;
|
||||
jump_session = ssh_new();
|
||||
if (jump_session == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
jump_session->proxy_root = false;
|
||||
/* Reset the global variable if it was previously 1 */
|
||||
if (session->proxy_root) {
|
||||
proxy_disconnect = 0;
|
||||
}
|
||||
|
||||
/* Pop first jump that will be used by the following thread */
|
||||
jis = ssh_list_pop_head(struct ssh_jump_info_struct *,
|
||||
session->opts.proxy_jumps);
|
||||
if (jis == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received");
|
||||
ssh_free(jump_session);
|
||||
goto fail;
|
||||
}
|
||||
jump_thread_data->next_jump = jis;
|
||||
/* Move remaining to the jump session without reallocation.
|
||||
* The list in the new jump_session is just allocated so empty */
|
||||
empty_list = jump_session->opts.proxy_jumps;
|
||||
jump_session->opts.proxy_jumps = session->opts.proxy_jumps;
|
||||
session->opts.proxy_jumps = empty_list;
|
||||
|
||||
/* Pop the callbacks for the first jump */
|
||||
cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *,
|
||||
session->opts.proxy_jumps_user_cb);
|
||||
/* empty is ok */
|
||||
jump_thread_data->next_cb = cb;
|
||||
/* Move remaining to the jump session without reallocation.
|
||||
* The list in the new jump_session is just allocated so empty */
|
||||
empty_list = jump_session->opts.proxy_jumps_user_cb;
|
||||
jump_session->opts.proxy_jumps_user_cb = session->opts.proxy_jumps_user_cb;
|
||||
session->opts.proxy_jumps_user_cb = empty_list;
|
||||
|
||||
ssh_options_set(jump_session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&session->common.log_verbosity);
|
||||
|
||||
jump_thread_data->next_port = session->opts.port;
|
||||
jump_thread_data->next_hostname = strdup(session->opts.host);
|
||||
|
||||
jump_thread_data->fd = pair[0];
|
||||
pair[0] = SSH_INVALID_SOCKET;
|
||||
jump_thread_data->session = jump_session;
|
||||
/* transferred to the jump_thread_data */
|
||||
jump_session = NULL;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
"Starting proxy thread to host %s port %d user %s, callbacks=%p",
|
||||
jump_thread_data->next_jump->hostname,
|
||||
jump_thread_data->next_jump->port,
|
||||
jump_thread_data->next_jump->username,
|
||||
(void *)jump_thread_data->next_cb);
|
||||
|
||||
rc = pthread_create(&jump_thread, NULL, jump_thread_func, jump_thread_data);
|
||||
if (rc != 0) {
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
|
||||
ssh_set_error(s->session,
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Creating new thread failed: %s",
|
||||
ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
SAFE_FREE(jump_thread_data);
|
||||
return SSH_ERROR;
|
||||
goto fail;
|
||||
}
|
||||
/* ownership passed to the thread */
|
||||
jump_thread_data = NULL;
|
||||
|
||||
rc = pthread_detach(jump_thread);
|
||||
if (rc != 0) {
|
||||
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
|
||||
|
||||
ssh_set_error(s->session,
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Failed to detach thread: %s",
|
||||
ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
SAFE_FREE(jump_thread_data);
|
||||
return SSH_ERROR;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"ProxyJump connection pipe: [%d,%d]",
|
||||
"ProxyJump connection thread %lu started pipe: [%d,%d]",
|
||||
(unsigned long)jump_thread,
|
||||
pair[0],
|
||||
pair[1]);
|
||||
|
||||
@@ -1511,6 +1552,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
if (rc != SSH_OK) {
|
||||
return rc;
|
||||
}
|
||||
pair[1] = SSH_INVALID_SOCKET;
|
||||
|
||||
s->fd_is_socket = 1;
|
||||
h = ssh_socket_get_poll_handle(s);
|
||||
@@ -1525,6 +1567,16 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
}
|
||||
|
||||
return SSH_OK;
|
||||
|
||||
fail:
|
||||
if (pair[0] != SSH_INVALID_SOCKET) {
|
||||
close(pair[0]);
|
||||
}
|
||||
if (pair[1] != SSH_INVALID_SOCKET) {
|
||||
close(pair[1]);
|
||||
}
|
||||
free_jump_thread_data(jump_thread_data);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
#endif /* HAVE_PTHREAD */
|
||||
|
||||
@@ -394,6 +394,7 @@ if (CLIENT_TESTING OR SERVER_TESTING)
|
||||
# Allow to auth with bob's public keys on alice and doe account
|
||||
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY)
|
||||
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY)
|
||||
configure_file(keys/id_ecdsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/frank/.ssh/authorized_keys @ONLY)
|
||||
|
||||
# append ECDSA public key
|
||||
file(READ keys/id_ecdsa.pub CONTENTS)
|
||||
|
||||
@@ -376,7 +376,7 @@ torture_auth_autopubkey_protected_auth_function (const char *prompt, char *buf,
|
||||
assert_int_equal(echo, 0);
|
||||
assert_int_equal(verify, 0);
|
||||
|
||||
expected_id = ssh_path_expand_escape(data->session, "%d/id_rsa_protected");
|
||||
expected_id = ssh_path_expand_escape(data->session, "%d/.ssh/id_rsa_protected");
|
||||
assert_true(expected_id != NULL);
|
||||
|
||||
rc = ssh_userauth_publickey_auto_get_current_identity(data->session, &id);
|
||||
@@ -429,7 +429,7 @@ static void torture_auth_autopubkey_protected(void **state) {
|
||||
|
||||
/* Try id_rsa_protected first.
|
||||
*/
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/id_rsa_protected");
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/.ssh/id_rsa_protected");
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
rc = ssh_connect(session);
|
||||
|
||||
@@ -100,13 +100,10 @@ static int session_setup(void **state)
|
||||
static int session_setup_ssh_dir(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
const char *no_home = "~/.no_ssh";
|
||||
int rc;
|
||||
|
||||
session_setup(state);
|
||||
|
||||
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, no_home);
|
||||
assert_ssh_return_code(s->ssh.session, rc);
|
||||
s->ssh.session->opts.homedir = strdup("~/.no_ssh");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <errno.h>
|
||||
#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),
|
||||
|
||||
@@ -121,6 +121,50 @@ static int authenticate(ssh_session jump_session, void *user)
|
||||
return ssh_userauth_publickey_auto(jump_session, NULL, NULL);
|
||||
}
|
||||
|
||||
static int authenticate_doe(ssh_session jump_session, void *user)
|
||||
{
|
||||
ssh_key pkey = NULL;
|
||||
char bob_ssh_key[1024];
|
||||
struct passwd *pwd = NULL;
|
||||
int rc;
|
||||
|
||||
(void)user;
|
||||
|
||||
pwd = getpwnam("bob");
|
||||
assert_non_null(pwd);
|
||||
|
||||
snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir);
|
||||
|
||||
rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
rc = ssh_userauth_publickey(jump_session, NULL, pkey);
|
||||
ssh_key_free(pkey);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int authenticate_frank(ssh_session jump_session, void *user)
|
||||
{
|
||||
ssh_key pkey = NULL;
|
||||
char bob_ssh_key[1024];
|
||||
struct passwd *pwd = NULL;
|
||||
int rc;
|
||||
|
||||
(void)user;
|
||||
|
||||
pwd = getpwnam("bob");
|
||||
assert_non_null(pwd);
|
||||
|
||||
snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_ecdsa", pwd->pw_dir);
|
||||
|
||||
rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
rc = ssh_userauth_publickey(jump_session, NULL, pkey);
|
||||
ssh_key_free(pkey);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void torture_proxyjump_multiple_jump(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
@@ -129,11 +173,10 @@ static void torture_proxyjump_multiple_jump(void **state)
|
||||
const char *address = torture_server_address(AF_INET);
|
||||
int rc;
|
||||
socket_t fd;
|
||||
|
||||
struct ssh_jump_callbacks_struct c = {
|
||||
.before_connection = before_connection,
|
||||
.verify_knownhost = verify_knownhost,
|
||||
.authenticate = authenticate
|
||||
.authenticate = authenticate,
|
||||
};
|
||||
|
||||
rc = snprintf(proxyjump_buf,
|
||||
@@ -177,14 +220,14 @@ static void torture_proxyjump_multiple_sshd_jump(void **state)
|
||||
struct ssh_jump_callbacks_struct c = {
|
||||
.before_connection = before_connection,
|
||||
.verify_knownhost = verify_knownhost,
|
||||
.authenticate = authenticate,
|
||||
.authenticate = authenticate_doe,
|
||||
};
|
||||
|
||||
torture_setup_sshd_servers(state, false);
|
||||
|
||||
rc = snprintf(proxyjump_buf,
|
||||
sizeof(proxyjump_buf),
|
||||
"alice@%s:22,alice@%s:22",
|
||||
"doe@%s:22,doe@%s:22",
|
||||
address,
|
||||
address1);
|
||||
if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) {
|
||||
@@ -222,17 +265,22 @@ static void torture_proxyjump_multiple_sshd_users_jump(void **state)
|
||||
int rc;
|
||||
socket_t fd;
|
||||
|
||||
struct ssh_jump_callbacks_struct c = {
|
||||
struct ssh_jump_callbacks_struct c1 = {
|
||||
.before_connection = before_connection,
|
||||
.verify_knownhost = verify_knownhost,
|
||||
.authenticate = authenticate,
|
||||
.authenticate = authenticate_doe,
|
||||
};
|
||||
struct ssh_jump_callbacks_struct c2 = {
|
||||
.before_connection = before_connection,
|
||||
.verify_knownhost = verify_knownhost,
|
||||
.authenticate = authenticate_frank,
|
||||
};
|
||||
|
||||
torture_setup_sshd_servers(state, false);
|
||||
|
||||
rc = snprintf(proxyjump_buf,
|
||||
sizeof(proxyjump_buf),
|
||||
"doe@%s:22,alice@%s:22",
|
||||
"doe@%s:22,frank@%s:22",
|
||||
address,
|
||||
address1);
|
||||
if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) {
|
||||
@@ -240,9 +288,9 @@ static void torture_proxyjump_multiple_sshd_users_jump(void **state)
|
||||
}
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf);
|
||||
assert_ssh_return_code(session, rc);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c1);
|
||||
assert_ssh_return_code(session, rc);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c2);
|
||||
assert_ssh_return_code(session, rc);
|
||||
|
||||
rc = ssh_connect(session);
|
||||
|
||||
@@ -11,19 +11,37 @@
|
||||
|
||||
#define MAX_XFER_BUF_SIZE 16384
|
||||
|
||||
#define DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(TEST_NAME) \
|
||||
{ \
|
||||
#TEST_NAME, \
|
||||
TEST_NAME, \
|
||||
session_setup, \
|
||||
session_teardown, \
|
||||
NULL \
|
||||
}, \
|
||||
{ \
|
||||
#TEST_NAME"_proxyjump", \
|
||||
TEST_NAME, \
|
||||
session_proxyjump_setup, \
|
||||
session_teardown, \
|
||||
NULL \
|
||||
}
|
||||
|
||||
static int sshd_setup(void **state)
|
||||
{
|
||||
torture_setup_sshd_server(state, false);
|
||||
torture_setup_sshd_servers(state, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sshd_teardown(void **state)
|
||||
{
|
||||
/* this will take care of the server1 teardown too */
|
||||
torture_teardown_sshd_server(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int session_setup(void **state)
|
||||
static int session_setup_helper(void **state, bool with_proxyjump)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
struct passwd *pwd = NULL;
|
||||
@@ -35,11 +53,15 @@ static int session_setup(void **state)
|
||||
rc = setuid(pwd->pw_uid);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
s->ssh.session = torture_ssh_session(s,
|
||||
TORTURE_SSH_SERVER,
|
||||
NULL,
|
||||
TORTURE_SSH_USER_ALICE,
|
||||
NULL);
|
||||
if (with_proxyjump) {
|
||||
s->ssh.session = torture_ssh_session_proxyjump();
|
||||
} else {
|
||||
s->ssh.session = torture_ssh_session(s,
|
||||
TORTURE_SSH_SERVER,
|
||||
NULL,
|
||||
TORTURE_SSH_USER_ALICE,
|
||||
NULL);
|
||||
}
|
||||
assert_non_null(s->ssh.session);
|
||||
|
||||
s->ssh.tsftp = torture_sftp_session(s->ssh.session);
|
||||
@@ -48,6 +70,16 @@ static int session_setup(void **state)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
return session_setup_helper(state, false);
|
||||
}
|
||||
|
||||
static int session_proxyjump_setup(void **state)
|
||||
{
|
||||
return session_setup_helper(state, true);
|
||||
}
|
||||
|
||||
static int session_teardown(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
@@ -722,37 +754,18 @@ int torture_run_tests(void)
|
||||
{
|
||||
int rc;
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_read_file,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_read_more_than_cap,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_write_file,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_write_more_than_cap,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_read_negative,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_write_negative,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_read_unordered_wait,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_aio_write_unordered_wait,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_file),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(
|
||||
torture_sftp_aio_read_more_than_cap),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_file),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(
|
||||
torture_sftp_aio_write_more_than_cap),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_negative),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_negative),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(
|
||||
torture_sftp_aio_read_unordered_wait),
|
||||
DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(
|
||||
torture_sftp_aio_write_unordered_wait),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
@@ -2,4 +2,4 @@ users:x:9000:
|
||||
sshd:x:65531:
|
||||
nobody:x:65533:
|
||||
nogroup:x:65534:nobody
|
||||
root:x:65532:
|
||||
root:x:0:
|
||||
|
||||
@@ -2,7 +2,8 @@ bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh
|
||||
alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh
|
||||
charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh
|
||||
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
|
||||
|
||||
141
tests/torture.c
141
tests/torture.c
@@ -389,6 +389,143 @@ failed:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* always return verification successful */
|
||||
static int verify_knownhost_trust_all(UNUSED_PARAM(ssh_session jump_session),
|
||||
UNUSED_PARAM(void *user))
|
||||
{
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a session connected to server via proxyjump
|
||||
*
|
||||
* @param[in] state A pointer to a pointer to an initialized torture_state
|
||||
* structure
|
||||
*
|
||||
* @warning It is expected that both sshd servers are setup before calling
|
||||
* this, see torture_setup_sshd_server() and
|
||||
* torture_setup_sshd_servers()
|
||||
*
|
||||
* TODO: If needed, in future, we can extend this function to:
|
||||
* - allow caller to pass server host port, user, password similar to
|
||||
* torture_ssh_session() or club this with that function
|
||||
*
|
||||
* - allow caller to customize jump hosts and callbacks for each of them
|
||||
*/
|
||||
ssh_session torture_ssh_session_proxyjump(void)
|
||||
{
|
||||
/*
|
||||
* We'll setup the connection chain:
|
||||
* - client
|
||||
* - jump host 1: doe (sshd server, IPV4)
|
||||
* - jump host 2: alice (sshd server1, IPV6)
|
||||
* - server: alice (sshd server, IPV4)
|
||||
*/
|
||||
char jump_host_list[1024] = {0};
|
||||
int jump_host_count = 2;
|
||||
const char *jump_host_1_address = torture_server_address(AF_INET);
|
||||
const char *jump_host_2_address = torture_server1_address(AF_INET6);
|
||||
struct ssh_jump_callbacks_struct jump_host_callbacks = {
|
||||
.before_connection = NULL,
|
||||
.verify_knownhost = verify_knownhost_trust_all,
|
||||
.authenticate = NULL,
|
||||
};
|
||||
|
||||
ssh_session session = NULL;
|
||||
bool process_config = false;
|
||||
int rc, i;
|
||||
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
fprintf(stderr, "Failed to create new ssh session\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr,
|
||||
"Failed to set session host: %s\n",
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr,
|
||||
"Failed to set session user: %s\n",
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr,
|
||||
"Failed to set process config option: %s\n",
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = snprintf(jump_host_list,
|
||||
sizeof(jump_host_list),
|
||||
"doe@%s:22,alice@%s:22",
|
||||
jump_host_1_address,
|
||||
jump_host_2_address);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "snprintf failed: %s\n", strerror(errno));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (rc >= (int)sizeof(jump_host_list)) {
|
||||
fprintf(stderr, "Insufficient jump host list buffer size\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, jump_host_list);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr,
|
||||
"Failed to set jump hosts for the session: %s\n",
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < jump_host_count; ++i) {
|
||||
rc = ssh_options_set(session,
|
||||
SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND,
|
||||
&jump_host_callbacks);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr,
|
||||
"Failed to set jump callbacks for jump host %d: %s\n",
|
||||
i + 1,
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
rc = ssh_connect(session);
|
||||
if (rc != SSH_OK) {
|
||||
fprintf(stderr,
|
||||
"Failed to connect to ssh server: %s\n",
|
||||
ssh_get_error(session));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
rc = ssh_userauth_publickey_auto(session, NULL, NULL);
|
||||
if (rc != SSH_AUTH_SUCCESS) {
|
||||
fprintf(stderr, "Public key authentication did not succeed\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return session;
|
||||
|
||||
failed:
|
||||
if (ssh_is_connected(session)) {
|
||||
ssh_disconnect(session);
|
||||
}
|
||||
ssh_free(session);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef WITH_SERVER
|
||||
|
||||
ssh_bind torture_ssh_bind(const char *addr,
|
||||
@@ -833,7 +970,7 @@ torture_setup_create_sshd_config(void **state, bool pam, bool second_sshd)
|
||||
"TrustedUserCAKeys %s\n"
|
||||
"\n"
|
||||
"LogLevel DEBUG3\n"
|
||||
"Subsystem sftp %s -l DEBUG2\n"
|
||||
"Subsystem sftp %s -l DEBUG3 -e\n"
|
||||
"\n"
|
||||
"PasswordAuthentication yes\n"
|
||||
"PubkeyAuthentication yes\n"
|
||||
@@ -873,7 +1010,7 @@ torture_setup_create_sshd_config(void **state, bool pam, bool second_sshd)
|
||||
"TrustedUserCAKeys %s\n" /* Trusted CA */
|
||||
"\n"
|
||||
"LogLevel DEBUG3\n"
|
||||
"Subsystem sftp %s -l DEBUG2\n" /* SFTP server */
|
||||
"Subsystem sftp %s -l DEBUG3 -e\n" /* SFTP server */
|
||||
"\n"
|
||||
"PasswordAuthentication yes\n"
|
||||
"PubkeyAuthentication yes\n"
|
||||
|
||||
@@ -115,6 +115,8 @@ ssh_session torture_ssh_session(struct torture_state *s,
|
||||
const char *user,
|
||||
const char *password);
|
||||
|
||||
ssh_session torture_ssh_session_proxyjump(void);
|
||||
|
||||
ssh_bind torture_ssh_bind(const char *addr,
|
||||
const unsigned int port,
|
||||
enum ssh_keytypes_e key_type,
|
||||
|
||||
@@ -25,7 +25,7 @@ extern LIBSSH_THREAD int ssh_log_level;
|
||||
#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa"
|
||||
#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521"
|
||||
#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com"
|
||||
#define USER_KNOWN_HOSTS "%d/my_known_hosts"
|
||||
#define USER_KNOWN_HOSTS "%d/.ssh/my_known_hosts"
|
||||
#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts"
|
||||
#define BIND_ADDRESS "::1"
|
||||
|
||||
@@ -159,6 +159,8 @@ extern LIBSSH_THREAD int ssh_log_level;
|
||||
"\tProxyJump jumpbox:2222\n" \
|
||||
"Host two-step\n" \
|
||||
"\tProxyJump u1@first:222,u2@second:33\n" \
|
||||
"Host three-step\n" \
|
||||
"\tProxyJump u1@first:222,u2@second:33,u3@third:444\n" \
|
||||
"Host none\n" \
|
||||
"\tProxyJump none\n" \
|
||||
"Host only-command\n" \
|
||||
@@ -1172,6 +1174,23 @@ static void torture_config_proxyjump(void **state,
|
||||
"u1",
|
||||
"222");
|
||||
|
||||
/* Three step jump */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "three-step");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root,
|
||||
"third",
|
||||
"u3",
|
||||
"444");
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root->next,
|
||||
"second",
|
||||
"u2",
|
||||
"33");
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root->next->next,
|
||||
"first",
|
||||
"u1",
|
||||
"222");
|
||||
|
||||
/* none */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "none");
|
||||
@@ -1237,6 +1256,13 @@ static void torture_config_proxyjump(void **state,
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first");
|
||||
|
||||
/* Three step jump */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "three-step");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -l u1 -p 222 -J u2@second:33,u3@third:444 -W '[%h]:%p' first");
|
||||
|
||||
/* none */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "none");
|
||||
|
||||
@@ -696,6 +696,82 @@ static void torture_knownhosts_algorithms_global(void **state)
|
||||
ssh_free(session);
|
||||
}
|
||||
|
||||
static int setup_bad_knownhosts_file(void **state)
|
||||
{
|
||||
char *tmp_file = NULL;
|
||||
size_t nwritten;
|
||||
FILE *fp = NULL;
|
||||
int rc = 0;
|
||||
|
||||
tmp_file = torture_create_temp_file(TMP_FILE_NAME);
|
||||
assert_non_null(tmp_file);
|
||||
|
||||
*state = tmp_file;
|
||||
|
||||
fp = fopen(tmp_file, "w");
|
||||
assert_non_null(fp);
|
||||
|
||||
nwritten = fwrite(LOCALHOST_DEFAULT_ED25519,
|
||||
sizeof(char),
|
||||
strlen(LOCALHOST_DEFAULT_ED25519),
|
||||
fp);
|
||||
if (nwritten != strlen(LOCALHOST_DEFAULT_ED25519)) {
|
||||
rc = -1;
|
||||
goto close_fp;
|
||||
}
|
||||
|
||||
nwritten = fwrite("\n", sizeof(char), 1, fp);
|
||||
if (nwritten != 1) {
|
||||
rc = -1;
|
||||
goto close_fp;
|
||||
}
|
||||
|
||||
#define LOCALHOST_BAD_LINE "localhost \n"
|
||||
nwritten = fwrite(LOCALHOST_BAD_LINE,
|
||||
sizeof(char),
|
||||
strlen(LOCALHOST_BAD_LINE),
|
||||
fp);
|
||||
if (nwritten != strlen(LOCALHOST_BAD_LINE)) {
|
||||
rc = -1;
|
||||
goto close_fp;
|
||||
}
|
||||
|
||||
close_fp:
|
||||
fclose(fp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void torture_knownhosts_has_entry(void **state)
|
||||
{
|
||||
const char *knownhosts_file = *state;
|
||||
enum ssh_known_hosts_e found;
|
||||
ssh_session session;
|
||||
bool process_config = false;
|
||||
struct ssh_knownhosts_entry *entry = NULL;
|
||||
|
||||
session = ssh_new();
|
||||
assert_non_null(session);
|
||||
|
||||
/* This makes sure the global configuration file is not processed */
|
||||
ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config);
|
||||
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "localhost");
|
||||
/* This makes sure the current-user's known hosts are not used */
|
||||
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null");
|
||||
ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file);
|
||||
|
||||
/* Error is expected -- this tests the memory is not leaked from this
|
||||
* test case */
|
||||
found = ssh_session_has_known_hosts_entry(session);
|
||||
assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR);
|
||||
|
||||
found = ssh_session_get_known_hosts_entry(session, &entry);
|
||||
assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR);
|
||||
assert_null(entry);
|
||||
|
||||
ssh_free(session);
|
||||
}
|
||||
#endif /* _WIN32 There is no /dev/null on Windows */
|
||||
|
||||
int torture_run_tests(void) {
|
||||
@@ -738,6 +814,9 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global,
|
||||
setup_knownhosts_file,
|
||||
teardown_knownhosts_file),
|
||||
cmocka_unit_test_setup_teardown(torture_knownhosts_has_entry,
|
||||
setup_bad_knownhosts_file,
|
||||
teardown_knownhosts_file),
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ static void torture_get_user_home_dir(void **state) {
|
||||
|
||||
(void) state;
|
||||
|
||||
user = ssh_get_user_home_dir();
|
||||
user = ssh_get_user_home_dir(NULL);
|
||||
assert_non_null(user);
|
||||
#ifndef _WIN32
|
||||
assert_string_equal(user, pwd->pw_dir);
|
||||
@@ -288,7 +288,8 @@ static void torture_path_expand_escape(void **state) {
|
||||
const char *s = "%d/%h/%p/by/%r";
|
||||
char *e;
|
||||
|
||||
session->opts.sshdir = strdup("guru");
|
||||
/* Set the homedir here to prevent querying the NSS DB */
|
||||
session->opts.homedir = strdup("guru");
|
||||
session->opts.host = strdup("meditation");
|
||||
session->opts.port = 0;
|
||||
session->opts.username = strdup("root");
|
||||
@@ -310,9 +311,10 @@ static void torture_path_expand_known_hosts(void **state) {
|
||||
ssh_session session = *state;
|
||||
char *tmp;
|
||||
|
||||
session->opts.sshdir = strdup("/home/guru/.ssh");
|
||||
/* Set the homedir here to prevent querying the NSS DB */
|
||||
session->opts.homedir = strdup("/home/guru");
|
||||
|
||||
tmp = ssh_path_expand_escape(session, "%d/known_hosts");
|
||||
tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts");
|
||||
assert_non_null(tmp);
|
||||
assert_string_equal(tmp, "/home/guru/.ssh/known_hosts");
|
||||
free(tmp);
|
||||
@@ -322,9 +324,10 @@ static void torture_path_expand_percent(void **state) {
|
||||
ssh_session session = *state;
|
||||
char *tmp;
|
||||
|
||||
session->opts.sshdir = strdup("/home/guru/.ssh");
|
||||
/* Set the homedir here to prevent querying the NSS DB */
|
||||
session->opts.homedir = strdup("/home/guru");
|
||||
|
||||
tmp = ssh_path_expand_escape(session, "%d/config%%1");
|
||||
tmp = ssh_path_expand_escape(session, "%d/.ssh/config%%1");
|
||||
assert_non_null(tmp);
|
||||
assert_string_equal(tmp, "/home/guru/.ssh/config%1");
|
||||
free(tmp);
|
||||
|
||||
@@ -839,6 +839,7 @@ static void torture_options_get_identity(void **state)
|
||||
char *identity = NULL;
|
||||
int rc;
|
||||
|
||||
/* This adds an identity to the head of the list and returns */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1");
|
||||
assert_true(rc == 0);
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity);
|
||||
@@ -856,6 +857,48 @@ static void torture_options_get_identity(void **state)
|
||||
assert_non_null(identity);
|
||||
assert_string_equal(identity, "identity2");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
/* Iterate over all of the identities */
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "identity2");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "identity1");
|
||||
SAFE_FREE(identity);
|
||||
|
||||
/* here are the default identities */
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "%d/.ssh/id_ed25519");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "%d/.ssh/id_ecdsa");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "%d/.ssh/id_rsa");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
#ifdef WITH_FIDO2
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "%d/.ssh/id_ed25519_sk");
|
||||
ssh_string_free_char(identity);
|
||||
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_string_equal(identity, "%d/.ssh/id_ecdsa_sk");
|
||||
ssh_string_free_char(identity);
|
||||
#endif /* WITH_FIDO2 */
|
||||
|
||||
rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity);
|
||||
assert_int_equal(rc, SSH_EOF);
|
||||
}
|
||||
|
||||
static void torture_options_set_global_knownhosts(void **state)
|
||||
@@ -2067,25 +2110,25 @@ static void torture_options_apply (void **state)
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
/* append the defaults; this list is copied from ssh_new@src/session.c */
|
||||
id = ssh_path_expand_escape(session, "%d/id_ed25519");
|
||||
id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519");
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
#ifdef HAVE_ECC
|
||||
id = ssh_path_expand_escape(session, "%d/id_ecdsa");
|
||||
id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa");
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
#endif
|
||||
id = ssh_path_expand_escape(session, "%d/id_rsa");
|
||||
id = ssh_path_expand_escape(session, "%d/.ssh/id_rsa");
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
#ifdef WITH_FIDO2
|
||||
/* Add security key identities */
|
||||
id = ssh_path_expand_escape(session, "%d/id_ed25519_sk");
|
||||
id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519_sk");
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
#ifdef HAVE_ECC
|
||||
id = ssh_path_expand_escape(session, "%d/id_ecdsa_sk");
|
||||
id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa_sk");
|
||||
rc = ssh_list_append(awaited_list, id);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
#endif /* HAVE_ECC */
|
||||
|
||||
Reference in New Issue
Block a user