Compare commits

...

9 Commits

Author SHA1 Message Date
Francesco Rollo
90b07e2c18 refactor(server): Warn about config override behavior in bind APIs
- Add a warning to ssh_bind_listen() clarifying that it implicitly
calls ssh_bind_options_parse_config(), which may override options
previously set via ssh_bind_options_set().

- Add a warning to ssh_bind_options_set() and ssh_bind_config_parse_string()
explaining that options may be overridden if configuration files are parsed
afterwards, either implicitly via ssh_bind_listen() or by an explicit call to
ssh_bind_options_parse_config().

Signed-off-by: Francesco <eferollo@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:36:59 +01:00
Francesco Rollo
edbd929fa2 feat(server): Add support for -o option argument in server example
Allow passing server configuration options via the -o flag and expose
ssh_bind_config_parse_string() as a public API.

Signed-off-by: Francesco <eferollo@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:36:58 +01:00
Himaneesh Mishra
38932b74c0 docs: reduce Doxygen warnings in libsshpp.hpp
Signed-off-by: Himaneesh Mishra <himaneeshmishra@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:35:41 +01:00
Mingyuan Li
60d6179eaa tests: Add opendir handle exhaustion test for sftpserver
Add torture_server_sftp_opendir_handles_exhaustion test that
exercises the error path in process_opendir() when all SFTP
handles are occupied. This covers the memory leak fix for
h->name that was missing in the sftp_handle_alloc() failure path.

The test exhausts all 256 handle slots with sftp_open(), then
verifies that sftp_opendir() fails gracefully without crashing
or leaking memory.

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:33:36 +01:00
Mingyuan Li
0d9b2c68cc sftpserver: Fix memory leak of h->name in process_opendir error path
When sftp_handle_alloc() fails in process_opendir(), the error path
frees the handle struct h but does not free h->name which was
allocated by strdup(). This causes a memory leak every time the
server runs out of available SFTP handles while processing an
opendir request.

Also add a missing NULL check for the strdup() call itself to
handle out-of-memory conditions gracefully.

This is the same class of bug that was fixed in process_open() by
commit db7f101d (CVE-2025-5449), but was missed in process_opendir().

Signed-off-by: Mingyuan Li <2560359315@qq.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:33:35 +01:00
Madhav Vasisth
adc2462329 docs: clarify ssh-agent API usage and lifecycle
Clarify the session-coupled nature of the ssh-agent interface,
document lifecycle and ownership expectations of agent-related
objects, and describe common error cases and limitations.

No functional changes.

Signed-off-by: Madhav Vasisth <mv2363@srmist.edu.in>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:32:39 +01:00
Pavol Žáčik
0bff33c790 gss-kex: Fix memory leaks in ssh_gssapi_check_client_config
Upon unsuccessful alloc of the gssapi context, the function
would return early without freeing the supported OID set.

With opts->gss_client_identity enabled, the function would
not free the client_id allocated by gss_import_name.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:30:53 +01:00
Pavol Žáčik
47e9b5536a gss-kex: Release output_token and mic on error paths
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:30:53 +01:00
Pavol Žáčik
2f1f474e27 gssapi: Free both_supported on a new error path
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-24 10:30:52 +01:00
12 changed files with 468 additions and 116 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 callers 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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 callers responsibility to ensure the correct
* order of API calls if explicit options must take
* precedence.
*/
int
ssh_bind_options_set(ssh_bind sshbind,

View File

@@ -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");

View File

@@ -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),