mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-03-24 20:40:09 +09:00
Compare commits
15 Commits
libssh-0.1
...
90b07e2c18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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:
|
||||
|
||||
@@ -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,24 +119,65 @@ 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);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -676,11 +676,20 @@ 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
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
10
src/misc.c
10
src/misc.c
@@ -2454,7 +2454,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 +2464,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 +2472,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 +2480,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 +2491,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));
|
||||
|
||||
@@ -2401,6 +2401,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,
|
||||
|
||||
@@ -1343,6 +1343,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 +1359,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");
|
||||
|
||||
@@ -1435,7 +1435,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 +1515,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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
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:::::
|
||||
|
||||
@@ -33,6 +33,7 @@ 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_server_fuzzer)
|
||||
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user