mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-03-24 20:40:09 +09:00
Compare commits
31 Commits
libssh-0.1
...
4dfcdd96b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dfcdd96b8 | ||
|
|
9d36b9dd81 | ||
|
|
afa21334b4 | ||
|
|
a2ebc7ea9b | ||
|
|
1ab8a35c5d | ||
|
|
8782fcec18 | ||
|
|
8d563f90f3 | ||
|
|
6a5e298cec | ||
|
|
163e1b059b | ||
|
|
e16018491e | ||
|
|
c26e9298e3 | ||
|
|
3c0567cb67 | ||
|
|
00d1903bf6 | ||
|
|
bc2a483aa1 | ||
|
|
5ad8dda6f6 | ||
|
|
d680b8ea8a | ||
|
|
90b07e2c18 | ||
|
|
edbd929fa2 | ||
|
|
38932b74c0 | ||
|
|
60d6179eaa | ||
|
|
0d9b2c68cc | ||
|
|
adc2462329 | ||
|
|
0bff33c790 | ||
|
|
47e9b5536a | ||
|
|
2f1f474e27 | ||
|
|
18d7a3967c | ||
|
|
d45ce10c83 | ||
|
|
a7fd80795e | ||
|
|
f8cba20859 | ||
|
|
f13a8d7ced | ||
|
|
c0963b3417 |
11
CHANGELOG
11
CHANGELOG
@@ -2,6 +2,17 @@ CHANGELOG
|
||||
=========
|
||||
|
||||
version 0.12.0 (released 2026-02-10)
|
||||
* Security:
|
||||
* CVE-2025-14821: libssh loads configuration files from the C:\etc directory
|
||||
on Windows
|
||||
* CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request()
|
||||
* CVE-2026-0965: Possible Denial of Service when parsing unexpected
|
||||
configuration files
|
||||
* CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input
|
||||
* CVE-2026-0967: Specially crafted patterns could cause DoS
|
||||
* CVE-2026-0968: OOB Read in sftp_parse_longname()
|
||||
* libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP
|
||||
extensions
|
||||
* Deprecations and removals:
|
||||
* Bumped minimal RSA key size to 1024 bits
|
||||
* New functionality:
|
||||
|
||||
@@ -108,7 +108,6 @@ if (DOXYGEN_FOUND)
|
||||
packet_struct,
|
||||
pem_get_password_struct,
|
||||
ssh_tokens_st,
|
||||
sftp_attributes_struct,
|
||||
sftp_client_message_struct,
|
||||
sftp_dir_struct,
|
||||
sftp_ext_struct,
|
||||
|
||||
@@ -337,6 +337,7 @@ static void batch_shell(ssh_session session)
|
||||
static int client(ssh_session session)
|
||||
{
|
||||
int auth = 0;
|
||||
int authenticated = 0;
|
||||
char *banner = NULL;
|
||||
int state;
|
||||
|
||||
@@ -369,16 +370,28 @@ static int client(ssh_session session)
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssh_userauth_none(session, NULL);
|
||||
banner = ssh_get_issue_banner(session);
|
||||
if (banner) {
|
||||
printf("%s\n", banner);
|
||||
free(banner);
|
||||
}
|
||||
auth = authenticate_console(session);
|
||||
if (auth != SSH_AUTH_SUCCESS) {
|
||||
auth = ssh_userauth_none(session, NULL);
|
||||
if (auth == SSH_AUTH_SUCCESS) {
|
||||
authenticated = 1;
|
||||
} else if (auth == SSH_AUTH_ERROR) {
|
||||
fprintf(stderr,
|
||||
"Authentication error during none auth: %s\n",
|
||||
ssh_get_error(session));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
auth = authenticate_console(session);
|
||||
if (auth != SSH_AUTH_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmds[0] == NULL) {
|
||||
shell(session);
|
||||
} else {
|
||||
|
||||
@@ -107,6 +107,14 @@ static struct argp_option options[] = {
|
||||
.doc = "Set the authorized keys file.",
|
||||
.group = 0
|
||||
},
|
||||
{
|
||||
.name = "option",
|
||||
.key = 'o',
|
||||
.arg = "OPTION",
|
||||
.flags = 0,
|
||||
.doc = "Set server configuration option [-o OptionName=Value]",
|
||||
.group = 0
|
||||
},
|
||||
{
|
||||
.name = "user",
|
||||
.key = 'u',
|
||||
@@ -158,6 +166,9 @@ parse_opt(int key, char *arg, struct argp_state *state)
|
||||
case 'a':
|
||||
strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1);
|
||||
break;
|
||||
case 'o':
|
||||
ssh_bind_config_parse_string(sshbind, arg);
|
||||
break;
|
||||
case 'u':
|
||||
strncpy(username, arg, sizeof(username) - 1);
|
||||
break;
|
||||
@@ -194,7 +205,7 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
{
|
||||
int key;
|
||||
|
||||
while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) {
|
||||
while((key = getopt(argc, argv, "a:e:k:o:p:P:r:u:v")) != -1) {
|
||||
if (key == 'p') {
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg);
|
||||
} else if (key == 'k') {
|
||||
@@ -205,6 +216,8 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg);
|
||||
} else if (key == 'a') {
|
||||
strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1);
|
||||
} else if (key == 'o') {
|
||||
ssh_bind_config_parse_string(sshbind, optarg);
|
||||
} else if (key == 'u') {
|
||||
strncpy(username, optarg, sizeof(username) - 1);
|
||||
} else if (key == 'P') {
|
||||
@@ -222,6 +235,7 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
"libssh %s -- a Secure Shell protocol implementation\n"
|
||||
"\n"
|
||||
" -a, --authorizedkeys=FILE Set the authorized keys file.\n"
|
||||
" -o, --option=OPTION Set server configuration option (e.g., -o OptionName=Value).\n"
|
||||
" -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n"
|
||||
" -k, --hostkey=FILE Set a host key. Can be used multiple times.\n"
|
||||
" Implies no default keys.\n"
|
||||
|
||||
@@ -74,6 +74,13 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief SSH agent connection context.
|
||||
*
|
||||
* This structure represents a connection to an SSH authentication agent.
|
||||
* It is used by libssh to communicate with the agent for operations such
|
||||
* as listing available identities and signing data during authentication.
|
||||
*/
|
||||
struct ssh_agent_struct {
|
||||
struct ssh_socket_struct *sock;
|
||||
ssh_buffer ident;
|
||||
@@ -82,13 +89,24 @@ struct ssh_agent_struct {
|
||||
};
|
||||
|
||||
/* agent.c */
|
||||
|
||||
/**
|
||||
* @brief Create a new ssh agent structure.
|
||||
*
|
||||
* @return An allocated ssh agent structure or NULL on error.
|
||||
* Creates and initializes an SSH agent context bound to the given
|
||||
* SSH session. The agent connection relies on the session configuration
|
||||
* (e.g. agent forwarding).
|
||||
*
|
||||
* @param session The SSH session the agent will be associated with.
|
||||
*
|
||||
* @return An allocated ssh agent structure on success, or NULL on error.
|
||||
*
|
||||
* @note This function does not start or manage an external agent
|
||||
* process; it only connects to an already running agent.
|
||||
*/
|
||||
struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session);
|
||||
|
||||
|
||||
void ssh_agent_close(struct ssh_agent_struct *agent);
|
||||
|
||||
/**
|
||||
@@ -101,23 +119,75 @@ void ssh_agent_free(struct ssh_agent_struct *agent);
|
||||
/**
|
||||
* @brief Check if the ssh agent is running.
|
||||
*
|
||||
* @param session The ssh session to check for the agent.
|
||||
* @param session The SSH session to check for agent availability.
|
||||
*
|
||||
* @return 1 if it is running, 0 if not.
|
||||
* @return 1 if an agent is available, 0 otherwise.
|
||||
*/
|
||||
int ssh_agent_is_running(struct ssh_session_struct *session);
|
||||
|
||||
uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session);
|
||||
|
||||
ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the first identity provided by the SSH agent.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param comment Optional pointer to receive the key comment.
|
||||
*
|
||||
* @return A public key on success, or NULL if no identities are available.
|
||||
*
|
||||
* @note The returned key is owned by the caller and must be freed
|
||||
* using ssh_key_free().
|
||||
*/
|
||||
ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the next identity provided by the SSH agent.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param comment Optional pointer to receive the key comment.
|
||||
*
|
||||
* @return A public key on success, or NULL if no further identities exist.
|
||||
*
|
||||
* @note The returned key is owned by the caller and must be freed
|
||||
* using ssh_key_free().
|
||||
*/
|
||||
ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Request the SSH agent to sign data using a public key.
|
||||
*
|
||||
* Asks the SSH agent to generate a signature over the provided data
|
||||
* using the specified public key.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param pubkey The public key identifying the signing identity.
|
||||
* @param data The data to be signed.
|
||||
*
|
||||
* @return A newly allocated ssh_string containing the signature on
|
||||
* success, or NULL on failure.
|
||||
*
|
||||
* @note This operation requires that the agent possesses the
|
||||
* corresponding private key and may prompt the user for
|
||||
* confirmation depending on agent configuration.
|
||||
*/
|
||||
ssh_string ssh_agent_sign_data(ssh_session session,
|
||||
const ssh_key pubkey,
|
||||
struct ssh_buffer_struct *data);
|
||||
/**
|
||||
* @brief Remove an identity from the SSH agent.
|
||||
*
|
||||
* @param session The SSH session.
|
||||
* @param key The public key to remove.
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
|
||||
*/
|
||||
int ssh_agent_remove_identity(ssh_session session,
|
||||
const ssh_key key);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -66,16 +66,6 @@ enum ssh_bind_config_opcode_e {
|
||||
*/
|
||||
int ssh_bind_config_parse_file(ssh_bind sshbind, 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
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
*/
|
||||
int ssh_bind_config_parse_string(ssh_bind bind, const char *input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#ifndef _BLF_H_
|
||||
#define _BLF_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//#include "includes.h"
|
||||
|
||||
#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H)
|
||||
|
||||
@@ -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 ) && \
|
||||
|
||||
@@ -9,6 +9,8 @@ Public domain.
|
||||
#ifndef CHACHA_H
|
||||
#define CHACHA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct chacha_ctx {
|
||||
uint32_t input[16];
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#ifndef CHACHA20_POLY1305_H
|
||||
#define CHACHA20_POLY1305_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHACHA20_BLOCKSIZE 64
|
||||
#define CHACHA20_KEYLEN 32
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -67,38 +67,60 @@ class Channel;
|
||||
*/
|
||||
#ifndef SSH_NO_CPP_EXCEPTIONS
|
||||
|
||||
/** @brief This class describes a SSH Exception object. This object can be thrown
|
||||
* by several SSH functions that interact with the network, and may fail because of
|
||||
* socket, protocol or memory errors.
|
||||
/** @brief This class describes a SSH Exception object. This object can be
|
||||
* thrown by several SSH functions that interact with the network, and
|
||||
* may fail because of socket, protocol or memory errors.
|
||||
*/
|
||||
class SshException{
|
||||
public:
|
||||
SshException(ssh_session csession){
|
||||
code=ssh_get_error_code(csession);
|
||||
description=std::string(ssh_get_error(csession));
|
||||
}
|
||||
SshException(const SshException &e){
|
||||
code=e.code;
|
||||
description=e.description;
|
||||
}
|
||||
/** @brief returns the Error code
|
||||
* @returns SSH_FATAL Fatal error happened (not recoverable)
|
||||
* @returns SSH_REQUEST_DENIED Request was denied by remote host
|
||||
* @see ssh_get_error_code
|
||||
*/
|
||||
int getCode(){
|
||||
return code;
|
||||
}
|
||||
/** @brief returns the error message of the last exception
|
||||
* @returns pointer to a c string containing the description of error
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
std::string getError(){
|
||||
return description;
|
||||
}
|
||||
private:
|
||||
int code;
|
||||
std::string description;
|
||||
class SshException {
|
||||
public:
|
||||
/** @brief Construct an exception from a libssh session error state.
|
||||
* Captures the current error code and error string associated with
|
||||
* the given session.
|
||||
*
|
||||
* @param[in] csession libssh session handle used to query error details.
|
||||
*
|
||||
* @see ssh_get_error_code
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
SshException(ssh_session csession)
|
||||
{
|
||||
code = ssh_get_error_code(csession);
|
||||
description = std::string(ssh_get_error(csession));
|
||||
}
|
||||
/** @brief Copy-construct an exception.
|
||||
*
|
||||
* @param[in] e Source exception.
|
||||
*/
|
||||
SshException(const SshException &e)
|
||||
{
|
||||
code = e.code;
|
||||
description = e.description;
|
||||
}
|
||||
/** @brief returns the Error code
|
||||
*
|
||||
* @returns `SSH_FATAL` Fatal error happened (not recoverable)
|
||||
* @returns `SSH_REQUEST_DENIED` Request was denied by remote host
|
||||
*
|
||||
* @see ssh_get_error_code
|
||||
*/
|
||||
int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
/** @brief returns the error message of the last exception
|
||||
*
|
||||
* @returns pointer to a c string containing the description of error
|
||||
*
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
std::string getError()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
private:
|
||||
int code;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
/** @internal
|
||||
@@ -134,9 +156,12 @@ public:
|
||||
c_session=NULL;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option cstring containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option cstring containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, const char *option){
|
||||
@@ -144,9 +169,12 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option long integer containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option long integer containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, long int option){
|
||||
@@ -154,9 +182,12 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option void pointer containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option void pointer containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, void *option){
|
||||
@@ -164,7 +195,9 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief connects to the remote host
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_connect
|
||||
*/
|
||||
void_throwable connect(){
|
||||
@@ -173,8 +206,11 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Authenticates automatically using public key
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_autopubkey
|
||||
*/
|
||||
int userauthPublickeyAuto(void){
|
||||
@@ -184,9 +220,13 @@ public:
|
||||
}
|
||||
/** @brief Authenticates using the "none" method. Prefer using autopubkey if
|
||||
* possible.
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_none
|
||||
*
|
||||
* @see Session::userauthAutoPubkey
|
||||
*/
|
||||
int userauthNone(){
|
||||
@@ -198,16 +238,16 @@ public:
|
||||
/**
|
||||
* @brief Authenticate through the "keyboard-interactive" method.
|
||||
*
|
||||
* @param[in] username The username to authenticate. You can specify NULL if
|
||||
* ssh_option_set_username() has been used. You cannot
|
||||
* try two different logins in a row.
|
||||
*
|
||||
* @param[in] username The username to authenticate. You can specify NULL
|
||||
* If ssh_option_set_username()has been used. You cannot
|
||||
* try two different logins in a row.
|
||||
* @param[in] submethods Undocumented. Set it to NULL.
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED,
|
||||
* SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`,
|
||||
* `SSH_AUTH_DENIED`, `SSH_AUTH_ERROR`, `SSH_AUTH_INFO`,
|
||||
* `SSH_AUTH_AGAIN`
|
||||
*
|
||||
* @see ssh_userauth_kbdint
|
||||
*/
|
||||
@@ -218,6 +258,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief Get the number of prompts (questions) the server has given.
|
||||
*
|
||||
* @returns The number of prompts.
|
||||
* @see ssh_userauth_kbdint_getnprompts
|
||||
*/
|
||||
@@ -228,17 +269,16 @@ public:
|
||||
/**
|
||||
* @brief Set the answer for a question from a message block.
|
||||
*
|
||||
* @param[in] index The index number of the prompt.
|
||||
* @param[in] answer The answer to give to the server. The answer MUST be
|
||||
* encoded UTF-8. It is up to the server how to interpret
|
||||
* the value and validate it. However, if you read the
|
||||
* answer in some other encoding, you MUST convert it to
|
||||
* UTF-8.
|
||||
* @param[in] index The index number of the prompt.
|
||||
* @param[in] answer The answer to give to the server. The answer MUST be
|
||||
* encoded UTF-8.It is up to the server how to interpret
|
||||
* the value and validate it. However, if you read the
|
||||
* answer in some other encoding, you MUST convert it to
|
||||
* UTF-8.
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns 0 on success, < 0 on error
|
||||
*
|
||||
* @see ssh_userauth_kbdint_setanswer
|
||||
*/
|
||||
int userauthKbdintSetAnswer(unsigned int index, const char *answer)
|
||||
@@ -248,12 +288,13 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @brief Authenticates using the password method.
|
||||
*
|
||||
* @param[in] password password to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_password
|
||||
*/
|
||||
int userauthPassword(const char *password){
|
||||
@@ -262,10 +303,14 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief Try to authenticate using the publickey method.
|
||||
*
|
||||
* @param[in] pubkey public key to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS if the pubkey is accepted,
|
||||
* @returns SSH_AUTH_DENIED if the pubkey is denied
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS` if the pubkey is accepted,
|
||||
* @returns `SSH_AUTH_DENIED` if the pubkey is denied
|
||||
*
|
||||
* @see ssh_userauth_try_pubkey
|
||||
*/
|
||||
int userauthTryPublickey(ssh_key pubkey){
|
||||
@@ -274,9 +319,12 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief Authenticates using the publickey method.
|
||||
*
|
||||
* @param[in] privkey private key to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_pubkey
|
||||
*/
|
||||
int userauthPublickey(ssh_key privkey){
|
||||
@@ -286,7 +334,9 @@ public:
|
||||
}
|
||||
|
||||
/** @brief Returns the available authentication methods from the server
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns Bitfield of available methods.
|
||||
* @see ssh_userauth_list
|
||||
*/
|
||||
@@ -302,8 +352,9 @@ public:
|
||||
ssh_disconnect(c_session);
|
||||
}
|
||||
/** @brief Returns the disconnect message from the server, if any
|
||||
* @returns pointer to the message, or NULL. Do not attempt to free
|
||||
* the pointer.
|
||||
*
|
||||
* @returns pointer to the message, or NULL. Do not attempt to free the
|
||||
* pointer.
|
||||
*/
|
||||
const char *getDisconnectMessage(){
|
||||
const char *msg=ssh_get_disconnect_message(c_session);
|
||||
@@ -312,25 +363,30 @@ public:
|
||||
/** @internal
|
||||
* @brief gets error message
|
||||
*/
|
||||
const char *getError(){
|
||||
return ssh_get_error(c_session);
|
||||
const char *getError()
|
||||
{
|
||||
return ssh_get_error(c_session);
|
||||
}
|
||||
/** @internal
|
||||
* @brief returns error code
|
||||
*/
|
||||
int getErrorCode(){
|
||||
return ssh_get_error_code(c_session);
|
||||
int getErrorCode()
|
||||
{
|
||||
return ssh_get_error_code(c_session);
|
||||
}
|
||||
/** @brief returns the file descriptor used for the communication
|
||||
*
|
||||
* @returns the file descriptor
|
||||
*
|
||||
* @warning if a proxycommand is used, this function will only return
|
||||
* one of the two file descriptors being used
|
||||
* one of the two file descriptors being used.
|
||||
* @see ssh_get_fd
|
||||
*/
|
||||
socket_t getSocket(){
|
||||
return ssh_get_fd(c_session);
|
||||
}
|
||||
/** @brief gets the Issue banner from the ssh server
|
||||
*
|
||||
* @returns the issue banner. This is generally a MOTD from server
|
||||
* @see ssh_get_issue_banner
|
||||
*/
|
||||
@@ -344,6 +400,7 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief returns the OpenSSH version (server) if possible
|
||||
*
|
||||
* @returns openssh version code
|
||||
* @see ssh_get_openssh_version
|
||||
*/
|
||||
@@ -351,6 +408,7 @@ public:
|
||||
return ssh_get_openssh_version(c_session);
|
||||
}
|
||||
/** @brief returns the version of the SSH protocol being used
|
||||
*
|
||||
* @returns the SSH protocol version
|
||||
* @see ssh_get_version
|
||||
*/
|
||||
@@ -358,9 +416,10 @@ public:
|
||||
return ssh_get_version(c_session);
|
||||
}
|
||||
/** @brief verifies that the server is known
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns Integer value depending on the knowledge of the
|
||||
* server key
|
||||
*
|
||||
* @returns Integer value depending on the knowledge of the server key
|
||||
* @see ssh_session_update_known_hosts
|
||||
*/
|
||||
int isServerKnown(){
|
||||
@@ -377,6 +436,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief copies options from a session to another
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_options_copy
|
||||
*/
|
||||
@@ -385,8 +445,11 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief parses a configuration file for options
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @param[in] file configuration file name
|
||||
*
|
||||
* @see ssh_options_parse_config
|
||||
*/
|
||||
void_throwable optionsParseConfig(const char *file){
|
||||
@@ -399,8 +462,8 @@ public:
|
||||
void silentDisconnect(){
|
||||
ssh_silent_disconnect(c_session);
|
||||
}
|
||||
/** @brief Writes the known host file with current
|
||||
* host key
|
||||
/** @brief Writes the known host file with current host key
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_write_knownhost
|
||||
*/
|
||||
@@ -411,11 +474,15 @@ public:
|
||||
}
|
||||
|
||||
/** @brief accept an incoming forward connection
|
||||
*
|
||||
* @param[in] timeout_ms timeout for waiting, in ms
|
||||
*
|
||||
* @returns new Channel pointer on the forward connection
|
||||
* @returns NULL in case of error
|
||||
*
|
||||
* @warning you have to delete this pointer after use
|
||||
* @see ssh_channel_forward_accept
|
||||
*
|
||||
* @see Session::listenForward
|
||||
*/
|
||||
inline Channel *acceptForward(int timeout_ms);
|
||||
@@ -439,6 +506,9 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
/** @internal
|
||||
* @brief Underlying libssh session handle.
|
||||
*/
|
||||
ssh_session c_session;
|
||||
|
||||
private:
|
||||
@@ -447,8 +517,7 @@ private:
|
||||
Session& operator=(const Session &);
|
||||
};
|
||||
|
||||
/** @brief the ssh::Channel class describes the state of an SSH
|
||||
* channel.
|
||||
/** @brief the ssh::Channel class describes the state of an SSH channel.
|
||||
* @see ssh_channel
|
||||
*/
|
||||
class Channel {
|
||||
@@ -464,11 +533,15 @@ public:
|
||||
}
|
||||
|
||||
/** @brief accept an incoming X11 connection
|
||||
*
|
||||
* @param[in] timeout_ms timeout for waiting, in ms
|
||||
*
|
||||
* @returns new Channel pointer on the X11 connection
|
||||
* @returns NULL in case of error
|
||||
*
|
||||
* @warning you have to delete this pointer after use
|
||||
* @see ssh_channel_accept_x11
|
||||
*
|
||||
* @see Channel::requestX11
|
||||
*/
|
||||
Channel *acceptX11(int timeout_ms){
|
||||
@@ -478,8 +551,10 @@ public:
|
||||
return newchan;
|
||||
}
|
||||
/** @brief change the size of a pseudoterminal
|
||||
*
|
||||
* @param[in] cols number of columns
|
||||
* @param[in] rows number of rows
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_channel_change_pty_size
|
||||
*/
|
||||
@@ -490,6 +565,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief closes a channel
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_channel_close
|
||||
*/
|
||||
@@ -536,6 +612,21 @@ public:
|
||||
bool isOpen(){
|
||||
return ssh_channel_is_open(channel) != 0;
|
||||
}
|
||||
/** @brief Open a TCP forward channel.
|
||||
*
|
||||
* @param[in] remotehost Remote host to connect to.
|
||||
* @param[in] remoteport Remote port to connect to.
|
||||
* @param[in] sourcehost Source address to report (can be NULL depending on
|
||||
* server policy).
|
||||
* @param[in] localport Local port to report (0 lets the server pick if
|
||||
* applicable).
|
||||
*
|
||||
* @returns `SSH_OK` (0) on success.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_open_forward
|
||||
*/
|
||||
int openForward(const char *remotehost, int remoteport,
|
||||
const char *sourcehost, int localport=0){
|
||||
int err=ssh_channel_open_forward(channel,remotehost,remoteport,
|
||||
@@ -549,20 +640,55 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
int poll(bool is_stderr=false){
|
||||
int err=ssh_channel_poll(channel,is_stderr);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
/** @brief Poll the channel for available data.
|
||||
*
|
||||
* @param[in] is_stderr If true, poll stderr stream; otherwise stdout.
|
||||
*
|
||||
* @returns Number of bytes available to read (>= 0).
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_poll
|
||||
*/
|
||||
int poll(bool is_stderr = false)
|
||||
{
|
||||
int err = ssh_channel_poll(channel, is_stderr);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
int read(void *dest, size_t count){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
if(count > 0x7fffffff)
|
||||
count = 0x7fffffff;
|
||||
err=ssh_channel_read_timeout(channel,dest,count,false,-1);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
/** @brief Read data from the channel (blocking).
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return of 0 indicates EOF/no data.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count)
|
||||
{
|
||||
int err;
|
||||
if (count > 0x7fffffff)
|
||||
count = 0x7fffffff;
|
||||
err = ssh_channel_read_timeout(channel, dest, count, false, -1);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel with a timeout.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] timeout Timeout in milliseconds. A negative value means
|
||||
* infinite timeout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return value of 0 indicates EOF.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count, int timeout){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -572,6 +698,22 @@ public:
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel with optional stderr selection and
|
||||
* timeout.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] is_stderr If true, read from the stderr stream; otherwise
|
||||
* read from stdout.
|
||||
* @param[in] timeout Timeout in milliseconds. A negative value means
|
||||
* infinite timeout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return value of 0 indicates EOF.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -581,6 +723,19 @@ public:
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel without blocking.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] is_stderr If true, read from the stderr stream; otherwise read
|
||||
* from stdout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return of 0 may indicate no data.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_nonblocking
|
||||
*/
|
||||
int readNonblocking(void *dest, size_t count, bool is_stderr=false){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -629,6 +784,18 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Request X11 forwarding for this channel.
|
||||
*
|
||||
* @param[in] single_connection If true, allow only a single X11 connection
|
||||
* for this channel; further X11 connections are
|
||||
* refused after the first is accepted.
|
||||
* @param[in] protocol X11 authentication protocol.
|
||||
* @param[in] cookie X11 authentication cookie.
|
||||
* @param[in] screen_number X11 screen number.
|
||||
*
|
||||
* @returns `SSH_OK` on success.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*/
|
||||
int requestX11(bool single_connection,
|
||||
const char *protocol, const char *cookie, int screen_number){
|
||||
int err=ssh_channel_request_x11(channel,single_connection,
|
||||
@@ -641,11 +808,16 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Writes on a channel
|
||||
* @param data data to write.
|
||||
* @param len number of bytes to write.
|
||||
* @param is_stderr write should be done on the stderr channel (server only)
|
||||
/**
|
||||
* @brief Writes on a channel
|
||||
*
|
||||
* @param[in] data data to write.
|
||||
* @param[in] len number of bytes to write.
|
||||
* @param[in] is_stderr write should be done on the stderr channel (server
|
||||
* only)
|
||||
*
|
||||
* @returns number of bytes written
|
||||
*
|
||||
* @throws SshException in case of error
|
||||
* @see ssh_channel_write
|
||||
* @see ssh_channel_write_stderr
|
||||
@@ -670,7 +842,13 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
/** @internal
|
||||
* @brief Parent session owning this channel.
|
||||
*/
|
||||
Session *session;
|
||||
/** @internal
|
||||
* @brief Underlying libssh channel handle.
|
||||
*/
|
||||
ssh_channel channel;
|
||||
|
||||
private:
|
||||
@@ -683,7 +861,6 @@ private:
|
||||
Channel &operator=(const Channel &);
|
||||
};
|
||||
|
||||
|
||||
inline Channel *Session::acceptForward(int timeout_ms){
|
||||
ssh_channel forward =
|
||||
ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL);
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#ifndef POLY1305_H
|
||||
#define POLY1305_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libssh/chacha20-poly1305-common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#ifndef SC25519_H
|
||||
#define SC25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define sc25519 crypto_sign_ed25519_ref_sc25519
|
||||
#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519
|
||||
#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#ifndef _SCP_H
|
||||
#define _SCP_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum ssh_scp_states {
|
||||
SSH_SCP_NEW, //Data structure just created
|
||||
SSH_SCP_WRITE_INITED, //Gave our intention to write
|
||||
|
||||
@@ -102,12 +102,31 @@ LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind,
|
||||
LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind,
|
||||
const char *filename);
|
||||
|
||||
LIBSSH_API int ssh_bind_config_parse_string(ssh_bind bind, const char *input);
|
||||
|
||||
/**
|
||||
* @brief Start listening to the socket.
|
||||
*
|
||||
* @param ssh_bind_o The ssh server bind to use.
|
||||
*
|
||||
* @return 0 on success, < 0 on error.
|
||||
*
|
||||
* @warning This function implicitly calls ssh_bind_options_parse_config()
|
||||
* to process system-wide and user configuration files unless
|
||||
* configuration processing was already performed explicitly
|
||||
* by the caller.\n
|
||||
* As a result, any options previously set (e.g., manually via
|
||||
* ssh_bind_options_set() or ssh_bind_config_parse_string()) may be
|
||||
* overridden by values from the configuration files.\n
|
||||
* To guarantee that explicitly set options take precedence,
|
||||
* callers of this function should either:
|
||||
* - call ssh_bind_options_parse_config() themselves before
|
||||
* setting options, or
|
||||
* - disable automatic config processing via
|
||||
* SSH_BIND_OPTIONS_PROCESS_CONFIG (set to false).
|
||||
*
|
||||
* @see ssh_bind_options_parse_config()
|
||||
* @see ssh_bind_options_set()
|
||||
*/
|
||||
LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o);
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -177,28 +177,113 @@ struct sftp_status_message_struct {
|
||||
char *langmsg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SFTP file attributes structure.
|
||||
*
|
||||
* This type represents file attributes. It is used both for
|
||||
* sending and receiving file attributes from the server.
|
||||
*
|
||||
* `flags` determines which of the struct fields are present.
|
||||
*
|
||||
* @see sftp_attributes_free()
|
||||
*/
|
||||
struct sftp_attributes_struct {
|
||||
/** File name */
|
||||
char *name;
|
||||
char *longname; /* ls -l output on openssh, not reliable else */
|
||||
|
||||
/** Extended name i.e output of `ls -l` (requires SFTP v3 with OpenSSH) */
|
||||
char *longname;
|
||||
|
||||
/** Determines which of the struct fields are present */
|
||||
uint32_t flags;
|
||||
|
||||
/** File type */
|
||||
uint8_t type;
|
||||
|
||||
/** File size (requires flag `SSH_FILEXFER_ATTR_SIZE`) */
|
||||
uint64_t size;
|
||||
|
||||
/** User ID (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_UIDGID`) */
|
||||
uint32_t uid;
|
||||
|
||||
/** Group ID (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_UIDGID`) */
|
||||
uint32_t gid;
|
||||
char *owner; /* set if openssh and version 4 */
|
||||
char *group; /* set if openssh and version 4 */
|
||||
|
||||
/**
|
||||
* File owner
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_OWNERGROUP`
|
||||
* or SFTP v3 with OpenSSH)
|
||||
*/
|
||||
char *owner;
|
||||
|
||||
/**
|
||||
* File group
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_OWNERGROUP`
|
||||
* or SFTP v3 with OpenSSH)
|
||||
*/
|
||||
char *group;
|
||||
|
||||
/** File permissions (requires flag `SSH_FILEXFER_ATTR_PERMISSIONS`) */
|
||||
uint32_t permissions;
|
||||
|
||||
/**
|
||||
* Access time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_ACCESSTIME`)
|
||||
*/
|
||||
uint64_t atime64;
|
||||
|
||||
/**
|
||||
* Access time
|
||||
* (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_ACMODTIME`)
|
||||
*/
|
||||
uint32_t atime;
|
||||
|
||||
/**
|
||||
* Access time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t atime_nseconds;
|
||||
|
||||
/**
|
||||
* Creation time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_CREATETIME`)
|
||||
*/
|
||||
uint64_t createtime;
|
||||
|
||||
/**
|
||||
* Creation time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t createtime_nseconds;
|
||||
|
||||
/**
|
||||
* Modification time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_MODIFYTIME`)
|
||||
*/
|
||||
uint64_t mtime64;
|
||||
|
||||
/**
|
||||
* Modification time
|
||||
* (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_ACMODTIME`)
|
||||
*/
|
||||
uint32_t mtime;
|
||||
|
||||
/**
|
||||
* Modification time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t mtime_nseconds;
|
||||
|
||||
/** ACL data (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_ACL`) */
|
||||
ssh_string acl;
|
||||
|
||||
/** Unused */
|
||||
uint32_t extended_count;
|
||||
|
||||
/** Unused */
|
||||
ssh_string extended_type;
|
||||
|
||||
/** Unused */
|
||||
ssh_string extended_data;
|
||||
};
|
||||
|
||||
@@ -206,27 +291,55 @@ struct sftp_attributes_struct {
|
||||
* @brief SFTP statvfs structure.
|
||||
*/
|
||||
struct sftp_statvfs_struct {
|
||||
uint64_t f_bsize; /** file system block size */
|
||||
uint64_t f_frsize; /** fundamental fs block size */
|
||||
uint64_t f_blocks; /** number of blocks (unit f_frsize) */
|
||||
uint64_t f_bfree; /** free blocks in file system */
|
||||
uint64_t f_bavail; /** free blocks for non-root */
|
||||
uint64_t f_files; /** total file inodes */
|
||||
uint64_t f_ffree; /** free file inodes */
|
||||
uint64_t f_favail; /** free file inodes for to non-root */
|
||||
uint64_t f_fsid; /** file system id */
|
||||
uint64_t f_flag; /** bit mask of f_flag values */
|
||||
uint64_t f_namemax; /** maximum filename length */
|
||||
/** file system block size */
|
||||
uint64_t f_bsize;
|
||||
|
||||
/** fundamental fs block size */
|
||||
uint64_t f_frsize;
|
||||
|
||||
/** number of blocks (unit f_frsize) */
|
||||
uint64_t f_blocks;
|
||||
|
||||
/** free blocks in file system */
|
||||
uint64_t f_bfree;
|
||||
|
||||
/** free blocks for non-root */
|
||||
uint64_t f_bavail;
|
||||
|
||||
/** total file inodes */
|
||||
uint64_t f_files;
|
||||
|
||||
/** free file inodes */
|
||||
uint64_t f_ffree;
|
||||
|
||||
/** free file inodes for non-root */
|
||||
uint64_t f_favail;
|
||||
|
||||
/** file system id */
|
||||
uint64_t f_fsid;
|
||||
|
||||
/** bit mask of f_flag values */
|
||||
uint64_t f_flag;
|
||||
|
||||
/** maximum filename length */
|
||||
uint64_t f_namemax;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SFTP limits structure.
|
||||
*/
|
||||
struct sftp_limits_struct {
|
||||
uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */
|
||||
uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */
|
||||
uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */
|
||||
uint64_t max_open_handles; /** maximum number of active handles allowed by server */
|
||||
/** maximum number of bytes in a single sftp packet */
|
||||
uint64_t max_packet_length;
|
||||
|
||||
/** maximum length in a SSH_FXP_READ packet */
|
||||
uint64_t max_read_length;
|
||||
|
||||
/** maximum length in a SSH_FXP_WRITE packet */
|
||||
uint64_t max_write_length;
|
||||
|
||||
/** maximum number of active handles allowed by server */
|
||||
uint64_t max_open_handles;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
88
src/agent.c
88
src/agent.c
@@ -635,3 +635,91 @@ ssh_string ssh_agent_sign_data(ssh_session session,
|
||||
|
||||
return sig_blob;
|
||||
}
|
||||
|
||||
int ssh_agent_remove_identity(ssh_session session,
|
||||
const ssh_key key)
|
||||
{
|
||||
ssh_buffer request = NULL;
|
||||
ssh_buffer reply = NULL;
|
||||
ssh_string key_blob = NULL;
|
||||
uint8_t type = 0;
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
if (session == NULL || key == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (session->agent == NULL) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"No agent connection available");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Connect to the agent if not already connected */
|
||||
if (!ssh_socket_is_open(session->agent->sock)) {
|
||||
if (agent_connect(session) < 0) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Could not connect to SSH agent");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
request = ssh_buffer_new();
|
||||
if (request == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_add_u8(request, SSH2_AGENTC_REMOVE_IDENTITY) < 0) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_pki_export_pubkey_blob(key, &key_blob) < 0) {
|
||||
ssh_set_error(session, SSH_FATAL, "Failed to export public key blob");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_add_ssh_string(request, key_blob) < 0) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
reply = ssh_buffer_new();
|
||||
if (reply == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (agent_talk(session, request, reply) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_get_u8(reply, &type) != sizeof(uint8_t)) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Failed to read agent reply type");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (agent_failed(type)) {
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure removing identity");
|
||||
goto fail;
|
||||
} else if (type != SSH_AGENT_SUCCESS) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Agent refused to remove identity: reply type %u",
|
||||
type);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = SSH_OK;
|
||||
|
||||
fail:
|
||||
SSH_STRING_FREE(key_blob);
|
||||
SSH_BUFFER_FREE(request);
|
||||
SSH_BUFFER_FREE(reply);
|
||||
return rc;
|
||||
}
|
||||
|
||||
24
src/auth.c
24
src/auth.c
@@ -1397,7 +1397,7 @@ int ssh_userauth_publickey_auto(ssh_session session,
|
||||
return SSH_AUTH_ERROR;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Starting authentication as a user %s",
|
||||
username ? username : session->opts.username);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -676,12 +676,21 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @brief Parse configuration string and set the options to the given bind session
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* @warning Options set via this function may be overridden if a configuration
|
||||
* file is parsed afterwards (e.g., by an implicit call to
|
||||
* ssh_bind_options_parse_config() inside ssh_bind_listen(), or by a
|
||||
* manual call to the same function) and contains the same options.\n
|
||||
* It is the caller’s responsibility to ensure the correct order of
|
||||
* API calls if explicit options must take precedence.
|
||||
*
|
||||
* @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)
|
||||
@@ -713,21 +722,29 @@ int ssh_bind_config_parse_string(ssh_bind bind, const char *input)
|
||||
}
|
||||
if (c == NULL) {
|
||||
/* should not happen, would mean a string without trailing '\0' */
|
||||
SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string");
|
||||
ssh_set_error(bind,
|
||||
SSH_FATAL,
|
||||
"No trailing '\\0' in config string");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
line_len = c - line_start;
|
||||
if (line_len > MAX_LINE_SIZE - 1) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Line %u too long: %zu characters",
|
||||
line_num,
|
||||
line_len);
|
||||
ssh_set_error(bind,
|
||||
SSH_FATAL,
|
||||
"Line %u too long: %zu characters",
|
||||
line_num,
|
||||
line_len);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
memcpy(line, line_start, line_len);
|
||||
line[line_len] = '\0';
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
|
||||
rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0);
|
||||
rv = ssh_bind_config_parse_line(bind,
|
||||
line,
|
||||
line_num,
|
||||
&parser_flags,
|
||||
seen,
|
||||
0);
|
||||
if (rv < 0) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
23
src/buffer.c
23
src/buffer.c
@@ -921,18 +921,10 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer,
|
||||
va_arg(ap, void *);
|
||||
count++; /* increase argument count */
|
||||
break;
|
||||
case 'F':
|
||||
case 'B':
|
||||
b = va_arg(ap, bignum);
|
||||
if (*p == 'F') {
|
||||
/* For padded bignum, we know the exact length */
|
||||
len = va_arg(ap, size_t);
|
||||
count++; /* increase argument count */
|
||||
needed_size += sizeof(uint32_t) + len;
|
||||
} else {
|
||||
/* The bignum bytes + 1 for possible padding */
|
||||
needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1;
|
||||
}
|
||||
/* The bignum bytes + 1 for possible padding */
|
||||
needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1;
|
||||
break;
|
||||
case 't':
|
||||
cstring = va_arg(ap, char *);
|
||||
@@ -1062,16 +1054,9 @@ ssh_buffer_pack_va(struct ssh_buffer_struct *buffer,
|
||||
rc = ssh_buffer_add_data(buffer, o.data, (uint32_t)len);
|
||||
o.data = NULL;
|
||||
break;
|
||||
case 'F':
|
||||
case 'B':
|
||||
b = va_arg(ap, bignum);
|
||||
if (*p == 'F') {
|
||||
len = va_arg(ap, size_t);
|
||||
count++; /* increase argument count */
|
||||
o.string = ssh_make_padded_bignum_string(b, len);
|
||||
} else {
|
||||
o.string = ssh_make_bignum_string(b);
|
||||
}
|
||||
o.string = ssh_make_bignum_string(b);
|
||||
if(o.string == NULL){
|
||||
rc = SSH_ERROR;
|
||||
break;
|
||||
@@ -1127,8 +1112,6 @@ ssh_buffer_pack_va(struct ssh_buffer_struct *buffer,
|
||||
* 'P': size_t, void * (len of data, pointer to data)
|
||||
* only pushes data.
|
||||
* 'B': bignum (pushed as SSH string)
|
||||
* 'F': bignum, size_t (bignum, padded to fixed length,
|
||||
* pushed as SSH string)
|
||||
* @returns SSH_OK on success
|
||||
* SSH_ERROR on error
|
||||
* @warning when using 'P' with a constant size (e.g. 8), do not
|
||||
|
||||
@@ -3517,7 +3517,7 @@ int ssh_channel_get_exit_state(ssh_channel channel,
|
||||
*pexit_signal = NULL;
|
||||
if (channel->exit.signal != NULL) {
|
||||
*pexit_signal = strdup(channel->exit.signal);
|
||||
if (pexit_signal == NULL) {
|
||||
if (*pexit_signal == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
42
src/config.c
42
src/config.c
@@ -258,7 +258,9 @@ local_parse_file(ssh_session session,
|
||||
|
||||
f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
|
||||
if (f == NULL) {
|
||||
/* The underlying function logs the reasons */
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
"Failed to open included configuration file %s",
|
||||
filename);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -542,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);
|
||||
@@ -564,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;
|
||||
@@ -580,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;
|
||||
@@ -1038,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:
|
||||
@@ -1064,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;
|
||||
|
||||
@@ -1152,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)) {
|
||||
@@ -1179,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,13 +244,31 @@ int ssh_config_parse_uri(const char *tok,
|
||||
if (*hostname == NULL) {
|
||||
goto error;
|
||||
}
|
||||
/* if not an ip, check syntax */
|
||||
rc = ssh_is_ipaddr(*hostname);
|
||||
if (rc == 0) {
|
||||
rc = ssh_check_hostname_syntax(*hostname);
|
||||
if (rc != SSH_OK) {
|
||||
if (strict) {
|
||||
/* if not an ip, check syntax */
|
||||
rc = ssh_is_ipaddr(*hostname);
|
||||
if (rc == 0) {
|
||||
rc = ssh_check_hostname_syntax(*hostname);
|
||||
if (rc != SSH_OK) {
|
||||
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 */
|
||||
|
||||
@@ -243,6 +243,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
|
||||
/* Get the server supported oids */
|
||||
rc = ssh_gssapi_server_oids(&supported);
|
||||
if (rc != SSH_OK) {
|
||||
gss_release_oid_set(&min_stat, &both_supported);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
@@ -730,7 +731,8 @@ int ssh_gssapi_check_client_config(ssh_session session)
|
||||
gssapi = calloc(1, sizeof(struct ssh_gssapi_struct));
|
||||
if (gssapi == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
return SSH_ERROR;
|
||||
ret = SSH_ERROR;
|
||||
break;
|
||||
}
|
||||
gssapi->server_creds = GSS_C_NO_CREDENTIAL;
|
||||
gssapi->client_creds = GSS_C_NO_CREDENTIAL;
|
||||
@@ -819,6 +821,11 @@ int ssh_gssapi_check_client_config(ssh_session session)
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER);
|
||||
|
||||
if (client_id != GSS_C_NO_NAME) {
|
||||
gss_release_name(&min_stat, &client_id);
|
||||
client_id = GSS_C_NO_NAME;
|
||||
}
|
||||
|
||||
SAFE_FREE(gssapi->canonic_user);
|
||||
SAFE_FREE(gssapi);
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ static int derive_hybrid_secret(ssh_session session,
|
||||
|
||||
rc = ssh_buffer_pack(combined_secret,
|
||||
"PP",
|
||||
MLKEM_SHARED_SECRET_SIZE,
|
||||
(size_t)MLKEM_SHARED_SECRET_SIZE,
|
||||
mlkem_shared_secret,
|
||||
ssh_string_len(ecdh_shared_secret),
|
||||
ssh_string_data(ecdh_shared_secret));
|
||||
@@ -244,7 +244,7 @@ int ssh_client_hybrid_mlkem_init(ssh_session session)
|
||||
"PP",
|
||||
ssh_string_len(crypto->mlkem_client_pubkey),
|
||||
ssh_string_data(crypto->mlkem_client_pubkey),
|
||||
CURVE25519_PUBKEY_SIZE,
|
||||
(size_t)CURVE25519_PUBKEY_SIZE,
|
||||
crypto->curve25519_client_pubkey);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
@@ -768,7 +768,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init)
|
||||
"PP",
|
||||
ssh_string_len(crypto->mlkem_ciphertext),
|
||||
ssh_string_data(crypto->mlkem_ciphertext),
|
||||
CURVE25519_PUBKEY_SIZE,
|
||||
(size_t)CURVE25519_PUBKEY_SIZE,
|
||||
crypto->curve25519_server_pubkey);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
|
||||
@@ -591,6 +591,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"GSSAPI(accept) integrity and mutual flags were not set");
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
goto error;
|
||||
}
|
||||
SSH_LOG(SSH_LOG_DEBUG, "token accepted");
|
||||
@@ -607,6 +608,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
"creating mic failed",
|
||||
maj_stat,
|
||||
min_stat);
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -621,15 +623,14 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
output_token.length,
|
||||
(size_t)output_token.length,
|
||||
output_token.value);
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_release_buffer(&min_stat, &mic);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error_oom(session);
|
||||
ssh_buffer_reinit(session->out_buffer);
|
||||
goto error;
|
||||
}
|
||||
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_release_buffer(&min_stat, &mic);
|
||||
|
||||
rc = ssh_packet_send(session);
|
||||
if (rc == SSH_ERROR) {
|
||||
goto error;
|
||||
|
||||
@@ -1688,11 +1688,6 @@ int ssh_make_sessionid(ssh_session session)
|
||||
switch (session->next_crypto->kex_type) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
rc = ssh_buffer_pack(buf,
|
||||
"F",
|
||||
session->next_crypto->shared_secret,
|
||||
SHA512_DIGEST_LEN);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
#ifdef HAVE_MLKEM1024
|
||||
@@ -1919,9 +1914,6 @@ int ssh_generate_session_keys(ssh_session session)
|
||||
switch (session->next_crypto->kex_type) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
k_string = ssh_make_padded_bignum_string(crypto->shared_secret,
|
||||
crypto->digest_len);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
#ifdef HAVE_MLKEM1024
|
||||
|
||||
@@ -507,3 +507,9 @@ LIBSSH_4_11_0 # Released
|
||||
sshsig_verify;
|
||||
} LIBSSH_4_10_0;
|
||||
|
||||
LIBSSH_AFTER_4_11_0
|
||||
{
|
||||
global:
|
||||
ssh_bind_config_parse_string;
|
||||
} LIBSSH_4_11_0;
|
||||
|
||||
|
||||
24
src/misc.c
24
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);
|
||||
@@ -2454,7 +2468,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
/* open first to avoid TOCTOU */
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to open a file %s for reading: %s",
|
||||
filename,
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
@@ -2464,7 +2478,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
/* Check the file is sensible for a configuration file */
|
||||
r = fstat(fd, &sb);
|
||||
if (r != 0) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to stat %s: %s",
|
||||
filename,
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
@@ -2472,7 +2486,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
return NULL;
|
||||
}
|
||||
if ((sb.st_mode & S_IFMT) != S_IFREG) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"The file %s is not a regular file: skipping",
|
||||
filename);
|
||||
close(fd);
|
||||
@@ -2480,7 +2494,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
}
|
||||
|
||||
if ((size_t)sb.st_size > max_file_size) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"The file %s is too large (%jd MB > %zu MB): skipping",
|
||||
filename,
|
||||
(intmax_t)sb.st_size / 1024 / 1024,
|
||||
@@ -2491,7 +2505,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
|
||||
f = fdopen(fd, "r");
|
||||
if (f == NULL) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to open a file %s for reading: %s",
|
||||
filename,
|
||||
ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
|
||||
@@ -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) {
|
||||
@@ -369,8 +378,8 @@ int ssh_options_set_algo(ssh_session session,
|
||||
* default ssh directory.\n
|
||||
* \n
|
||||
* The ssh directory is used for files like known_hosts
|
||||
* and identity (private and public key). It may include
|
||||
* "%s" which will be replaced by the user home
|
||||
* and identity (private and public key). It may start
|
||||
* with ~ which will be replaced by the user home
|
||||
* directory.
|
||||
*
|
||||
* - SSH_OPTIONS_KNOWNHOSTS:
|
||||
@@ -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;
|
||||
}
|
||||
@@ -2401,6 +2445,15 @@ static int ssh_bind_set_algo(ssh_bind sshbind,
|
||||
* not a pointer when it should have been a pointer, or if
|
||||
* its a pointer to a pointer when it should have just been
|
||||
* a pointer), then the behaviour is undefined.
|
||||
*
|
||||
* @warning Options set via this function may be overridden if a
|
||||
* configuration file is parsed afterwards (e.g., by an
|
||||
* implicit call to ssh_bind_options_parse_config() inside
|
||||
* ssh_bind_listen(), or by a manual call to the same
|
||||
* function) and contains the same options.\n
|
||||
* It is the caller’s responsibility to ensure the correct
|
||||
* order of API calls if explicit options must take
|
||||
* precedence.
|
||||
*/
|
||||
int
|
||||
ssh_bind_options_set(ssh_bind sshbind,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -489,7 +489,10 @@ sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending name %s, ID %" PRIu32,
|
||||
ssh_string_get_char(file),
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(1)) < 0 ||
|
||||
@@ -532,6 +535,7 @@ int sftp_reply_handle(sftp_client_message msg, ssh_string handle)
|
||||
ssh_log_hexdump("Sending handle:",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "packet ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_ssh_string(out, handle) < 0 ||
|
||||
@@ -565,7 +569,7 @@ int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr, ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
buffer_add_attributes(out, attr) < 0 ||
|
||||
@@ -653,7 +657,10 @@ int sftp_reply_names(sftp_client_message msg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending %d names, ID %" PRIu32,
|
||||
msg->attr_num,
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 ||
|
||||
@@ -705,8 +712,11 @@ sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status,
|
||||
ssh_string_get_char(s));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending status %d, message: %s, ID %" PRIu32,
|
||||
status,
|
||||
ssh_string_get_char(s),
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(status)) < 0 ||
|
||||
@@ -745,7 +755,10 @@ int sftp_reply_data(sftp_client_message msg, const void *data, int len)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending data, length: %d, ID %" PRIu32,
|
||||
len,
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, ntohl(len)) < 0 ||
|
||||
@@ -780,7 +793,7 @@ sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply, ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 ||
|
||||
@@ -810,7 +823,9 @@ int sftp_reply_version(sftp_client_message client_msg)
|
||||
ssh_buffer reply;
|
||||
int rc;
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending version packet, ID %" PRIu32,
|
||||
client_msg->id);
|
||||
|
||||
version = sftp->client_version;
|
||||
reply = ssh_buffer_new();
|
||||
@@ -1183,6 +1198,10 @@ process_read(sftp_client_message client_msg)
|
||||
ssh_log_hexdump("Processing read: handle:",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Offset %" PRIu64", length %" PRIu32,
|
||||
client_msg->offset,
|
||||
client_msg->len);
|
||||
|
||||
h = sftp_handle(sftp, handle);
|
||||
if (h != NULL && h->type == SFTP_FILE_HANDLE) {
|
||||
@@ -1241,6 +1260,10 @@ process_write(sftp_client_message client_msg)
|
||||
ssh_log_hexdump("Processing write: handle",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Offset %" PRIu64", length %" PRIu32,
|
||||
client_msg->offset,
|
||||
client_msg->len);
|
||||
|
||||
h = sftp_handle(sftp, handle);
|
||||
if (h != NULL && h->type == SFTP_FILE_HANDLE) {
|
||||
@@ -1343,6 +1366,15 @@ process_opendir(sftp_client_message client_msg)
|
||||
}
|
||||
h->dirp = dir;
|
||||
h->name = strdup(dir_name);
|
||||
if (h->name == NULL) {
|
||||
free(h);
|
||||
closedir(dir);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "failed to duplicate directory name");
|
||||
sftp_reply_status(client_msg,
|
||||
SSH_FX_FAILURE,
|
||||
"Failed to allocate new handle");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
h->type = SFTP_DIR_HANDLE;
|
||||
handle_s = sftp_handle_alloc(client_msg->sftp, h);
|
||||
|
||||
@@ -1350,6 +1382,7 @@ process_opendir(sftp_client_message client_msg)
|
||||
sftp_reply_handle(client_msg, handle_s);
|
||||
ssh_string_free(handle_s);
|
||||
} else {
|
||||
SAFE_FREE(h->name);
|
||||
free(h);
|
||||
closedir(dir);
|
||||
sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available");
|
||||
@@ -1958,7 +1991,10 @@ dispatch_sftp_request(sftp_client_message sftp_msg)
|
||||
sftp_server_message_callback handler = NULL;
|
||||
uint8_t type = sftp_client_message_get_type(sftp_msg);
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"processing request type: %" PRIu8 ", ID %" PRIu32,
|
||||
type,
|
||||
sftp_msg->id);
|
||||
|
||||
for (int i = 0; message_handlers[i].cb != NULL; i++) {
|
||||
if (type == message_handlers[i].type) {
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "libssh/sntrup761.h"
|
||||
#ifdef HAVE_SNTRUP761
|
||||
|
||||
#include "libssh/bignum.h"
|
||||
#include "libssh/buffer.h"
|
||||
#include "libssh/crypto.h"
|
||||
#include "libssh/dh.h"
|
||||
@@ -141,7 +140,7 @@ static int ssh_sntrup761x25519_build_k(ssh_session session)
|
||||
{
|
||||
unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE];
|
||||
unsigned char *k = ssk + SNTRUP761_SIZE;
|
||||
unsigned char hss[SHA512_DIGEST_LEN];
|
||||
void *shared_secret_data = NULL;
|
||||
int rc;
|
||||
|
||||
rc = ssh_curve25519_create_k(session, k);
|
||||
@@ -216,22 +215,27 @@ static int ssh_sntrup761x25519_build_k(ssh_session session)
|
||||
ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE);
|
||||
#endif
|
||||
|
||||
sha512(ssk, sizeof ssk, hss);
|
||||
|
||||
bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret);
|
||||
if (session->next_crypto->shared_secret == NULL) {
|
||||
ssh_string_burn(session->next_crypto->hybrid_shared_secret);
|
||||
ssh_string_free(session->next_crypto->hybrid_shared_secret);
|
||||
session->next_crypto->hybrid_shared_secret =
|
||||
ssh_string_new(SHA512_DIGEST_LEN);
|
||||
if (session->next_crypto->hybrid_shared_secret == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
rc = SSH_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
shared_secret_data =
|
||||
ssh_string_data(session->next_crypto->hybrid_shared_secret);
|
||||
|
||||
sha512(ssk, sizeof ssk, shared_secret_data);
|
||||
|
||||
#ifdef DEBUG_CRYPTO
|
||||
ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret);
|
||||
ssh_log_hexdump("Shared secret key", shared_secret_data, SHA512_DIGEST_LEN);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
cleanup:
|
||||
ssh_burn(ssk, sizeof ssk);
|
||||
ssh_burn(hss, sizeof hss);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -1435,7 +1436,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
|
||||
session = s->session;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Connecting to host %s port %d user %s through ProxyJump",
|
||||
session->opts.host,
|
||||
session->opts.port,
|
||||
@@ -1515,7 +1516,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
/* transferred to the jump_thread_data */
|
||||
jump_session = NULL;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Starting proxy thread to host %s port %d user %s, callbacks=%p",
|
||||
jump_thread_data->next_jump->hostname,
|
||||
jump_thread_data->next_jump->port,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -259,6 +259,20 @@ else()
|
||||
set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found")
|
||||
endif()
|
||||
|
||||
find_program(TINYSSHD_EXECUTABLE
|
||||
NAMES
|
||||
tinysshd
|
||||
PATHS
|
||||
/sbin
|
||||
/usr/sbin
|
||||
/usr/local/sbin)
|
||||
|
||||
if (TINYSSHD_EXECUTABLE)
|
||||
message(STATUS "Found TinySSH server: ${TINYSSHD_EXECUTABLE}")
|
||||
else()
|
||||
message(STATUS "Could NOT find TinySSH server")
|
||||
endif()
|
||||
|
||||
find_program(SSHD_EXECUTABLE
|
||||
NAME
|
||||
sshd
|
||||
@@ -391,6 +405,14 @@ if (CLIENT_TESTING OR SERVER_TESTING)
|
||||
file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
|
||||
file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
|
||||
|
||||
# TINYSSH Key Setup
|
||||
set(TINYSSH_KEY_DIR "${CMAKE_CURRENT_BINARY_DIR}/etc/tinyssh")
|
||||
file(MAKE_DIRECTORY ${TINYSSH_KEY_DIR})
|
||||
|
||||
# Copy the TINYSSH hostkeys
|
||||
file(COPY keys/ed25519.pk DESTINATION ${TINYSSH_KEY_DIR})
|
||||
file(COPY keys/.ed25519.sk DESTINATION ${TINYSSH_KEY_DIR})
|
||||
|
||||
# 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)
|
||||
@@ -419,6 +441,9 @@ if (CLIENT_TESTING OR SERVER_TESTING)
|
||||
file(READ keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub CONTENTS)
|
||||
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}")
|
||||
|
||||
# Create home directory for noneuser (for "none" authentication test)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/home/noneuser/.ssh)
|
||||
|
||||
file(READ keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub CONTENTS)
|
||||
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}")
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ if (WITH_PKCS11_URI)
|
||||
torture_auth_pkcs11)
|
||||
endif()
|
||||
|
||||
if (TINYSSHD_EXECUTABLE AND NCAT_EXECUTABLE)
|
||||
set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} torture_tinyssh)
|
||||
endif()
|
||||
|
||||
if (HAVE_PTHREAD)
|
||||
set(LIBSSH_CLIENT_TESTS
|
||||
${LIBSSH_CLIENT_TESTS}
|
||||
@@ -92,6 +96,12 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS})
|
||||
LINK_LIBRARIES ${TORTURE_LIBRARY} util
|
||||
)
|
||||
|
||||
if (_CLI_TEST STREQUAL "torture_tinyssh")
|
||||
target_compile_definitions(${_CLI_TEST} PRIVATE
|
||||
TINYSSH_KEYS_DIR="${TINYSSH_KEY_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (OSX)
|
||||
set_property(
|
||||
TEST
|
||||
|
||||
@@ -228,6 +228,44 @@ static void torture_auth_none_max_tries(void **state) {
|
||||
torture_update_sshd_config(state, "");
|
||||
}
|
||||
|
||||
static void torture_auth_none_success(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
const char *additional_config = "PermitEmptyPasswords yes\n"
|
||||
"PasswordAuthentication yes\n"
|
||||
"KbdInteractiveAuthentication no\n"
|
||||
"PubkeyAuthentication no\n"
|
||||
"AuthenticationMethods none\n";
|
||||
|
||||
ssh_session session = s->ssh.session;
|
||||
int rc;
|
||||
|
||||
torture_update_sshd_config(state, additional_config);
|
||||
|
||||
/* Use noneuser which has an empty password set in shadow.in
|
||||
* When PermitEmptyPasswords is yes and PasswordAuthentication is yes,
|
||||
* OpenSSH's userauth_none() internally calls mm_auth_password() with
|
||||
* an empty password, which succeeds for users with empty passwords.
|
||||
*/
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_NONEUSER);
|
||||
if (rc != SSH_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = ssh_connect(session);
|
||||
if (rc != SSH_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = ssh_userauth_none(session, NULL);
|
||||
assert_int_equal(rc, SSH_AUTH_SUCCESS);
|
||||
|
||||
cleanup:
|
||||
torture_update_sshd_config(state, "");
|
||||
if (rc != SSH_OK && rc != SSH_AUTH_SUCCESS) {
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
}
|
||||
}
|
||||
|
||||
static void torture_auth_pubkey(void **state) {
|
||||
struct torture_state *s = *state;
|
||||
@@ -1373,6 +1411,9 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_success,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_max_tries,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
@@ -1424,9 +1465,10 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_agent_identities_only_protected,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
@@ -1436,15 +1478,17 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_types_ecdsa_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_types_ed25519_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
#ifdef WITH_FIDO2
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ecdsa,
|
||||
pubkey_setup,
|
||||
@@ -1456,9 +1500,10 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_rsa_key_size_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_skip_none,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "libssh/callbacks.h"
|
||||
#include "libssh/libssh.h"
|
||||
#include <libssh/agent.h>
|
||||
#include "libssh/priv.h"
|
||||
|
||||
#include <errno.h>
|
||||
@@ -286,6 +287,96 @@ static void torture_auth_agent_forwarding(void **state)
|
||||
}
|
||||
}
|
||||
|
||||
static int agent_session_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
int verbosity = torture_libssh_verbosity();
|
||||
int rc;
|
||||
|
||||
s->ssh.ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.ssh.session);
|
||||
|
||||
rc = ssh_options_set(s->ssh.ssh.session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&verbosity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
/* No callbacks needed — only talking to the local agent.
|
||||
* The group setup already started the agent and loaded keys.
|
||||
* Do NOT call torture_setup_ssh_agent here — that would spawn
|
||||
* a second agent and overwrite SSH_AUTH_SOCK. */
|
||||
s->ssh.ssh.cb_state = NULL;
|
||||
s->ssh.ssh.callbacks = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
ssh_key key = NULL;
|
||||
char *comment = NULL;
|
||||
uint32_t count_before = 0;
|
||||
uint32_t count_after = 0;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
|
||||
assert_true(ssh_agent_is_running(session));
|
||||
|
||||
count_before = ssh_agent_get_ident_count(session);
|
||||
|
||||
assert_true(count_before > 0);
|
||||
|
||||
key = ssh_agent_get_first_ident(session, &comment);
|
||||
assert_non_null(key);
|
||||
assert_non_null(comment);
|
||||
|
||||
rc = ssh_agent_remove_identity(session, key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
count_after = ssh_agent_get_ident_count(session);
|
||||
assert_int_equal(count_after, count_before - 1);
|
||||
|
||||
ssh_key_free(key);
|
||||
ssh_string_free_char(comment);
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity_negative(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
|
||||
/* NULL key should return SSH_ERROR */
|
||||
rc = ssh_agent_remove_identity(session, NULL);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity_nonexistent(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
ssh_key key = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
assert_true(ssh_agent_is_running(session));
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(key);
|
||||
|
||||
/* Key not in agent should fail */
|
||||
rc = ssh_agent_remove_identity(session, key);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
ssh_key_free(key);
|
||||
}
|
||||
|
||||
/* Session setup function that configures SSH agent */
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
@@ -300,7 +391,6 @@ static int session_setup(void **state)
|
||||
/* Create a new session */
|
||||
s->ssh.ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.ssh.session);
|
||||
|
||||
rc = ssh_options_set(s->ssh.ssh.session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&verbosity);
|
||||
@@ -342,19 +432,32 @@ static int session_setup(void **state)
|
||||
int torture_run_tests(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_negative,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_nonexistent,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
/* Simplify the CMocka test filter handling */
|
||||
#if defined HAVE_CMOCKA_SET_TEST_FILTER
|
||||
cmocka_set_message_output(CM_OUTPUT_STDOUT);
|
||||
#endif
|
||||
|
||||
/* Apply test filtering */
|
||||
torture_filter_tests(tests);
|
||||
|
||||
rc = cmocka_run_group_tests(tests,
|
||||
@@ -365,5 +468,4 @@ int torture_run_tests(void)
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
328
tests/client/torture_tinyssh.c
Normal file
328
tests/client/torture_tinyssh.c
Normal file
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* This file is part of the SSH Library
|
||||
*
|
||||
* Copyright (c) 2026 by Your Bulitha Kawushika De Zoysa
|
||||
*
|
||||
* The SSH Library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* The SSH Library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the SSH Library; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
* MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "tests_config.h"
|
||||
|
||||
#define LIBSSH_STATIC
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/session.h"
|
||||
#include "torture.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TINYSSH_PIDFILE "tinyssh.pid"
|
||||
#define TINYSSH_PORT 22
|
||||
|
||||
/* TINYSSH Server Setup and Teardown */
|
||||
|
||||
static int tinyssh_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = NULL;
|
||||
char cmd[4096];
|
||||
char pid_path[1024];
|
||||
int rc;
|
||||
|
||||
torture_setup_socket_dir(state);
|
||||
s = *state;
|
||||
|
||||
snprintf(pid_path,
|
||||
sizeof(pid_path),
|
||||
"%s/%s",
|
||||
s->socket_dir,
|
||||
TINYSSH_PIDFILE);
|
||||
free(s->srv_pidfile);
|
||||
s->srv_pidfile = strdup(pid_path);
|
||||
if (s->srv_pidfile == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(cmd,
|
||||
sizeof(cmd),
|
||||
"%s -l %s %d -k -c \"%s %s -v %s\" "
|
||||
"> %s/tinyssh.log 2>&1 & echo $! > %s",
|
||||
NCAT_EXECUTABLE,
|
||||
TORTURE_SSH_SERVER,
|
||||
TINYSSH_PORT,
|
||||
TINYSSHD_EXECUTABLE,
|
||||
"",
|
||||
TINYSSH_KEYS_DIR,
|
||||
s->socket_dir,
|
||||
s->srv_pidfile);
|
||||
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Executing: %s\n", cmd);
|
||||
|
||||
rc = system(cmd);
|
||||
if (rc != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = torture_wait_for_daemon(15);
|
||||
if (rc != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tinyssh_teardown(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
torture_terminate_process(s->srv_pidfile);
|
||||
torture_teardown_socket_dir(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* LIBSSH Client Setup and Teardown */
|
||||
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
int verbosity = torture_libssh_verbosity();
|
||||
bool process_config = false;
|
||||
int port = TINYSSH_PORT;
|
||||
struct passwd *pwd = NULL;
|
||||
int rc;
|
||||
|
||||
pwd = getpwnam("bob");
|
||||
assert_non_null(pwd);
|
||||
|
||||
rc = setuid(pwd->pw_uid);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
s->ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.session);
|
||||
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "bob");
|
||||
ssh_options_set(s->ssh.session,
|
||||
SSH_OPTIONS_PROCESS_CONFIG,
|
||||
&process_config);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int session_teardown(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
if (s->ssh.session) {
|
||||
ssh_disconnect(s->ssh.session);
|
||||
ssh_free(s->ssh.session);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Algorithms Helper */
|
||||
|
||||
static void test_specific_algorithm(ssh_session session,
|
||||
const char *kex,
|
||||
const char *cipher,
|
||||
const char *hostkey,
|
||||
int expected_rc)
|
||||
{
|
||||
int rc;
|
||||
char data[256];
|
||||
size_t len_to_test[] = {1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15,
|
||||
16, 20, 31, 32, 33, 63, 64, 65, 100, 127, 128};
|
||||
unsigned int i;
|
||||
|
||||
/* Set Key Exchange */
|
||||
if (kex != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
/* Set Ciphers */
|
||||
if (cipher != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher);
|
||||
assert_ssh_return_code(session, rc);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
/* Set Hostkey */
|
||||
if (hostkey != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
rc = ssh_connect(session);
|
||||
|
||||
if (expected_rc == SSH_OK) {
|
||||
assert_ssh_return_code(session, rc);
|
||||
|
||||
if (cipher != NULL) {
|
||||
const char *used_cipher = ssh_get_cipher_out(session);
|
||||
assert_non_null(used_cipher);
|
||||
assert_string_equal(used_cipher, cipher);
|
||||
}
|
||||
|
||||
if (hostkey != NULL) {
|
||||
ssh_key pubkey = NULL;
|
||||
const char *type_str = NULL;
|
||||
|
||||
rc = ssh_get_server_publickey(session, &pubkey);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(pubkey);
|
||||
|
||||
type_str = ssh_key_type_to_char(ssh_key_type(pubkey));
|
||||
assert_non_null(type_str);
|
||||
assert_string_equal(type_str, hostkey);
|
||||
ssh_key_free(pubkey);
|
||||
}
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) {
|
||||
memset(data, 'A', len_to_test[i]);
|
||||
ssh_send_ignore(session, data);
|
||||
ssh_handle_packets(session, 50);
|
||||
}
|
||||
|
||||
rc = ssh_userauth_none(session, NULL);
|
||||
if (rc != SSH_OK) {
|
||||
rc = ssh_get_error_code(session);
|
||||
assert_int_equal(rc, SSH_REQUEST_DENIED);
|
||||
}
|
||||
|
||||
} else {
|
||||
assert_int_not_equal(rc, SSH_OK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test Cases */
|
||||
|
||||
static void torture_tinyssh_curve25519(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"curve25519-sha256",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_curve25519_libssh(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"curve25519-sha256@libssh.org",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_sntrup761(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"sntrup761x25519-sha512@openssh.com",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_chacha20(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
NULL,
|
||||
"chacha20-poly1305@openssh.com",
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_neg_cipher(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
|
||||
/* TinySSH does not support older ciphers like aes128-cbc.*/
|
||||
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
NULL,
|
||||
"aes128-cbc",
|
||||
NULL,
|
||||
SSH_ERROR);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_hostkey_ed25519(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session, NULL, NULL, "ssh-ed25519", SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_neg_kex(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
|
||||
/* TinySSH does not support legacy Diffie-Hellman groups or NIST curves.*/
|
||||
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"diffie-hellman-group1-sha1",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_ERROR);
|
||||
}
|
||||
|
||||
int torture_run_tests(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519_libssh,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_sntrup761,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_hostkey_ed25519,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_chacha20,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_cipher,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_kex,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
torture_filter_tests(tests);
|
||||
rc = cmocka_run_group_tests(tests, tinyssh_setup, tinyssh_teardown);
|
||||
|
||||
ssh_finalize();
|
||||
return rc;
|
||||
}
|
||||
@@ -2,3 +2,4 @@ bob:secret:sshd
|
||||
alice:secret:sshd
|
||||
charlie:secret:sshd
|
||||
doe:secret:sshd
|
||||
noneuser::sshd
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
noneuser:x:5004:9000:noneuser gecos:@HOMEDIR@/noneuser:/bin/sh
|
||||
sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin
|
||||
nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false
|
||||
root:x:0:0:root gecos:@HOMEDIR@:/bin/false
|
||||
|
||||
@@ -2,3 +2,4 @@ alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFl
|
||||
bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
noneuser:::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
|
||||
@@ -33,8 +44,10 @@ fuzzer(ssh_client_config_fuzzer)
|
||||
fuzzer(ssh_known_hosts_fuzzer)
|
||||
fuzzer(ssh_privkey_fuzzer)
|
||||
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
|
||||
131
tests/fuzz/ssh_sftp_attr_fuzzer.c
Normal file
131
tests/fuzz/ssh_sftp_attr_fuzzer.c
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/sftp.h"
|
||||
#include "libssh/sftp_priv.h"
|
||||
|
||||
#include "nallocinc.c"
|
||||
|
||||
/* SFTP protocol version constants */
|
||||
#define SFTP_PROTOCOL_VERSION_3 3
|
||||
#define SFTP_PROTOCOL_VERSION_4 4
|
||||
|
||||
/* Flags for sftp_parse_attr expectname parameter */
|
||||
#define SFTP_EXPECT_NAME 1
|
||||
#define SFTP_NO_NAME 0
|
||||
|
||||
/*
|
||||
* Helper to create a minimal sftp_session for fuzzing.
|
||||
* We don't use sftp_new() as it requires a real SSH connection.
|
||||
*/
|
||||
static sftp_session create_minimal_sftp_session(ssh_session session)
|
||||
{
|
||||
sftp_session sftp;
|
||||
|
||||
sftp = calloc(1, sizeof(struct sftp_session_struct));
|
||||
if (sftp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
sftp->session = session;
|
||||
|
||||
return sftp;
|
||||
}
|
||||
|
||||
static void _fuzz_finalize(void)
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
|
||||
nalloc_init(*argv[0]);
|
||||
|
||||
ssh_init();
|
||||
|
||||
atexit(_fuzz_finalize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
ssh_session session = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_buffer buffer = NULL;
|
||||
sftp_attributes attr = NULL;
|
||||
int versions[] = {
|
||||
SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3,
|
||||
SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4
|
||||
};
|
||||
int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME};
|
||||
size_t i;
|
||||
|
||||
/* Minimum bytes for a valid SFTP message */
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(nalloc_start(data, size) > 0);
|
||||
|
||||
/* Allocate shared resources once for all test iterations */
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
sftp = create_minimal_sftp_session(session);
|
||||
if (sftp == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buffer = ssh_buffer_new();
|
||||
if (buffer == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Main fuzzing target: sftp_parse_attr */
|
||||
/* Parses untrusted SFTP messages from client */
|
||||
/* Test all combinations (v3/v4, with/without name) */
|
||||
for (i = 0; i < (sizeof(versions) / sizeof(versions[0])); i++) {
|
||||
sftp->version = versions[i];
|
||||
|
||||
/* Reset and repopulate buffer for each iteration */
|
||||
ssh_buffer_reinit(buffer);
|
||||
if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) {
|
||||
attr = sftp_parse_attr(sftp, buffer, expectnames[i]);
|
||||
sftp_attributes_free(attr);
|
||||
attr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ssh_buffer_free(buffer);
|
||||
free(sftp);
|
||||
ssh_free(session);
|
||||
nalloc_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/keys/.ed25519.sk
Normal file
BIN
tests/keys/.ed25519.sk
Normal file
Binary file not shown.
1
tests/keys/ed25519.pk
Normal file
1
tests/keys/ed25519.pk
Normal file
@@ -0,0 +1 @@
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>C<EFBFBD><EFBFBD>K<EFBFBD>ݛ<EFBFBD>1<1C><>'j&<26>e<EFBFBD><65><EFBFBD><EF8A8D>
|
||||
@@ -1130,6 +1130,46 @@ static void torture_server_sftp_handles_exhaustion(void **state)
|
||||
}
|
||||
}
|
||||
|
||||
static void torture_server_sftp_opendir_handles_exhaustion(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
char name[128] = {0};
|
||||
sftp_file handles[SFTP_HANDLES] = {0};
|
||||
sftp_dir dir = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
sftp = tsftp->sftp;
|
||||
assert_non_null(sftp);
|
||||
|
||||
/* Occupy all handles with files */
|
||||
for (int i = 0; i < SFTP_HANDLES; i++) {
|
||||
snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i);
|
||||
handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700);
|
||||
assert_non_null(handles[i]);
|
||||
}
|
||||
|
||||
/* Opening a directory should fail gracefully without leaking h->name */
|
||||
dir = sftp_opendir(sftp, tsftp->testdir);
|
||||
assert_null(dir);
|
||||
|
||||
/* cleanup */
|
||||
for (int i = 0; i < SFTP_HANDLES; i++) {
|
||||
rc = sftp_close(handles[i]);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
}
|
||||
}
|
||||
|
||||
static void torture_server_sftp_handle_overrun(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
@@ -1290,6 +1330,9 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_opendir_handles_exhaustion,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
|
||||
@@ -86,3 +86,7 @@
|
||||
#cmakedefine PKCS11SPY "${PKCS11SPY}"
|
||||
#cmakedefine HAVE_SK_DUMMY 1
|
||||
#cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}"
|
||||
|
||||
/* TinySSH Executable */
|
||||
|
||||
#cmakedefine TINYSSHD_EXECUTABLE "${TINYSSHD_EXECUTABLE}"
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
#define TORTURE_SSH_USER_ALICE "alice"
|
||||
#define TORTURE_SSH_USER_CHARLIE "charlie"
|
||||
#define TORTURE_SSH_USER_NONEUSER "noneuser"
|
||||
|
||||
/* Used by main to communicate with parse_opt. */
|
||||
struct argument_s {
|
||||
|
||||
@@ -267,10 +267,10 @@ static void torture_ssh_buffer_bignum(void **state)
|
||||
bignum num = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
uint8_t verif[] = "\x00\x00\x00\x04" /* len 4 byte */
|
||||
"\x00\x00\x00\xff" /* padded 255 */
|
||||
"\x00\x00\x00\x02" /* len 2 byte */
|
||||
"\x00\xff"; /* padded 255 */
|
||||
uint8_t verif[] = "\x00\x00\x00\x02" /* len */
|
||||
"\x00\xff" /* pad, num */
|
||||
"\x00\x00\x00\x02" /* len */
|
||||
"\x00\xff"; /* pad, num */
|
||||
|
||||
(void)state;
|
||||
|
||||
@@ -283,20 +283,13 @@ static void torture_ssh_buffer_bignum(void **state)
|
||||
rc = bignum_set_word(num, 255);
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
rc = ssh_buffer_pack(buffer, "FB", num, (size_t)4, num);
|
||||
rc = ssh_buffer_pack(buffer, "BB", num, num);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
len = ssh_buffer_get_len(buffer);
|
||||
assert_int_equal(len, sizeof(verif) - 1);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) - 1);
|
||||
|
||||
/* negative test -- this number requires 3 bytes */
|
||||
rc = bignum_set_word(num, 256 * 256);
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
rc = ssh_buffer_pack(buffer, "FB", num, (size_t)2, num);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
bignum_safe_free(num);
|
||||
|
||||
SSH_BUFFER_FREE(buffer);
|
||||
|
||||
@@ -130,23 +130,23 @@ extern LIBSSH_THREAD int ssh_log_level;
|
||||
"ProxyJump = many-spaces.com\n" /* valid */
|
||||
|
||||
/* Match keyword */
|
||||
#define LIBSSH_TESTCONFIG_STRING10 \
|
||||
"Match host example\n" \
|
||||
"\tHostName example.com\n" \
|
||||
"Match host example1,example2\n" \
|
||||
"\tHostName exampleN\n" \
|
||||
"Match user guest\n" \
|
||||
"\tHostName guest.com\n" \
|
||||
"Match user tester host testhost\n" \
|
||||
"\tHostName testhost.com\n" \
|
||||
#define LIBSSH_TESTCONFIG_STRING10 \
|
||||
"Match host example\n" \
|
||||
"\tHostName example.com\n" \
|
||||
"Match host example1,example2\n" \
|
||||
"\tHostName exampleN\n" \
|
||||
"Match user guest\n" \
|
||||
"\tHostName guest.com\n" \
|
||||
"Match user tester host testhost\n" \
|
||||
"\tHostName testhost.com\n" \
|
||||
"Match !user tester host testhost\n" \
|
||||
"\tHostName nonuser-testhost.com\n" \
|
||||
"Match all\n" \
|
||||
"\tHostName all-matched.com\n" \
|
||||
/* Unsupported options */ \
|
||||
"Match originalhost example\n" \
|
||||
"\tHostName original-example.com\n" \
|
||||
"Match localuser guest\n" \
|
||||
"\tHostName nonuser-testhost.com\n" \
|
||||
"Match all\n" \
|
||||
"\tHostName all-matched.com\n" \
|
||||
"Match originalhost example\n" \
|
||||
"\tHostName original-example.com\n" \
|
||||
"\tUser originaluser\n" \
|
||||
"Match localuser guest\n" \
|
||||
"\tHostName local-guest.com\n"
|
||||
|
||||
/* ProxyJump */
|
||||
@@ -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);
|
||||
|
||||
@@ -421,3 +421,95 @@
|
||||
fun:gss_acquire_cred_from
|
||||
fun:gss_acquire_cred
|
||||
}
|
||||
|
||||
# Function mecherror_copy called in various
|
||||
# functions of the krb5 library copies entries
|
||||
# to the global error mapping table (mecherrmap m).
|
||||
{
|
||||
Global error mapping table in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
fun:mecherror_copy
|
||||
}
|
||||
|
||||
# Function add_error_table called in various
|
||||
# functions of the krb5 library adds entries
|
||||
# to a global list of error tables et_list.
|
||||
{
|
||||
Global list of error tables in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
fun:add_error_table
|
||||
}
|
||||
|
||||
# Function build_mechSet builds the global
|
||||
# gss_OID_set_desc g_mechSet which is only
|
||||
# free'd when initialized again.
|
||||
{
|
||||
Global OID set in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:build_mechSet
|
||||
}
|
||||
|
||||
# Function gssint_register_mechinfo()
|
||||
# called from gssint_mechglue_init() adds
|
||||
# entries to a global linked list g_mechList.
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:gssint_register_mechinfo*
|
||||
...
|
||||
fun:gssint_mechglue_init
|
||||
}
|
||||
|
||||
# Function addConfigEntry() called during
|
||||
# updateMechList() adds entries to
|
||||
# a global linked list g_mechList.
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:addConfigEntry
|
||||
...
|
||||
fun:updateMechList
|
||||
}
|
||||
|
||||
# Function loadInterMech() called during
|
||||
# updateMechList() loops through the global
|
||||
# linked list g_mechList and updates its entries
|
||||
# with heap-alloced "interposer fields".
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:loadInterMech
|
||||
...
|
||||
fun:updateMechList
|
||||
}
|
||||
|
||||
# Multiple krb5 functions call krb5int_open_plugin
|
||||
# which opens shared libraries using dlopen.
|
||||
# The plugin handle then seems to be stored in the
|
||||
# main krb5 context.
|
||||
{
|
||||
Plugin handles stored in the krb5 context
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:dlopen*
|
||||
...
|
||||
fun:krb5int_open_plugin
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user