Compare commits

...

11 Commits

Author SHA1 Message Date
Jakub Jelen
729a44e121 ci: Skip macos jobs on forks
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-03-24 10:58:22 +01:00
Jakub Jelen
051ac812db examples: Add warning about example code
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-03-24 10:58:11 +01:00
Haythem666
01772c4f79 pki: add ssh_key_type_and_hash_from_signature_name()
Merge ssh_key_type_from_signature_name() and ssh_key_hash_from_name()
into a single function ssh_key_type_and_hash_from_signature_name() to:

- Avoid double string comparisons on the same algorithm name
- Return SSH_ERROR on unknown/NULL input instead of silently returning SSH_DIGEST_AUTO
- Use strlen() before strcmp() to short-circuit string comparisons.

Handle GSSAPI "null" hostkey case in wrapper.c.
Add unit tests for the new function.

Fixes: https://gitlab.com/libssh/libssh-mirror/-/issues/355
Signed-off-by: Haythem666 <haythem.farhat@epfl.ch>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 10:50:39 +01:00
Manas Trivedi
9f7c596ca5 tests: add coverage for NULL session in ssh_channel_is_open
Signed-off-by: Manas Trivedi <manas.trivedi.020@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 10:15:06 +01:00
Manas Trivedi
34bbb48561 channels: add NULL session check in ssh_channel_is_open
Prevent potential NULL pointer dereference when accessing
channel->session->alive.

Signed-off-by: Manas Trivedi <manas.trivedi.020@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-24 10:15:05 +01:00
Jakub Jelen
f060583d6f tests: Generate coverage for fuzzing tests
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-19 22:16:17 +01:00
Bulitha Kawushika De Zoysa
a05b2b76be tests: initialize sftp test pointers to NULL
Signed-off-by: Bulitha Kawushika De Zoysa <bulithakaushika99@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-19 17:06:05 +01:00
Bulitha Kawushika De Zoysa
c9f34ac55f sftp: Add support for the users-groups-by-id@openssh.com OpenSSH extension on the server side.
Signed-off-by: Bulitha Kawushika De Zoysa <bulithakaushika99@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-19 17:06:04 +01:00
Ahmed hossam
bc24bba176 docs: Add documentation for test_server functions
Signed-off-by: Ahmed hossam <ahmed.hossambahig@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-19 17:03:42 +01:00
Colin Baumgarten
3154a4ab8d sftpserver: Fix client messages being ignored if sent at a high rate
When using OpenSSH scp to read files larger than a few hundred
kilobytes, downloads stall and never finish. A workaround is to
pass -Xnrequests=1 to scp, which will cause scp to only do a
single concurrent SFTP read request at a time.

The cause for the problem is that if SFTP client messages are
received at a high rate, sftp_channel_default_data_callback() will
potentially be called with multiple messages in the incoming data
buffer, but only the first message will be extracted and handled.

So add a loop to extract as many SFTP client messages as available
from the incoming data buffer.

Signed-off-by: Colin Baumgarten <colin.baumgarten@hubersuhner.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-17 12:01:15 +01:00
Sudharshan Hegde
9478de8082 doc: add missing Doxygen comments and fix documentation style
- Add missing @brief, @param, and @return docs across src/ and include/
- Fix blank lines between doc comments and function definitions
- Move function docs from headers to corresponding .c files
- Use named constants (SSH_OK, SSH_ERROR, SSH_TIMEOUT_INFINITE) in docs
- Fix parameter ordering in error.c, buffer.c, log.c docs
- Place #ifdef-guarded docs inside their respective #ifdef blocks

Signed-off-by: Sudharshan Hegde <sudharshanhegde68@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-17 11:46:11 +01:00
33 changed files with 1567 additions and 173 deletions

View File

@@ -784,8 +784,6 @@ coverity:
- mkdir obj && cd obj
only:
- branches@libssh/libssh-mirror
- branches@cryptomilk/libssh-mirror
- branches@jjelen/libssh-mirror
# TODO add -DFUZZ_TESTING=ON clang cant find _LLVMFuzzerInitialize on arm64
macos-m1:

View File

@@ -49,4 +49,4 @@ ALL_FUNC=$(echo "$FUNC_LINES" | sed -e "s/$F_CUT_BEFORE//g" -e "s/$F_CUT_AFTER//
ALL_FUNC=$(echo "$ALL_FUNC" | sort - | uniq | wc -l)
# percentage of the documented functions
awk "BEGIN {printf \"Documentation coverage is %.2f%\n\", 100 - (${UNDOC_FUNC}/${ALL_FUNC}*100)}"
awk "BEGIN {printf \"Documentation coverage is %.2f%%\n\", 100 - (${UNDOC_FUNC}/${ALL_FUNC}*100)}"

View File

@@ -1,4 +1,4 @@
/* This is a sample implementation of a libssh based SSH server */
/* This is a sample implementation of a libssh based SFTP server */
/*
Copyright 2014 Audrius Butkevicius
@@ -9,6 +9,28 @@ domain. This does not apply to the rest of the library though, but it is
allowed to cut-and-paste working code from this file to any license of
program.
The goal is to show the API in action.
!!! WARNING / ACHTUNG !!!
This is not a production-ready SFTP server implementation. While it demonstrates
how an SFTP server can be implemented on the SFTP layer and integrated into
existing SSH server, it lacks many steps int the authentication and
session establishment!
It allows to log in any user with hardcoded credentials below or with public
key provided from authorized keys file.
The resulting SFTP session keeps running under original user who runs the
example server and therefore the SFTP session has access to all files that are
accessible to the user running the server.
Real-world servers should at very least switch the user to unprivileged one
after authentication using setuid(). If some more restrictions are needed,
generally limiting what files should and should not be accessible, it is
recommended to use chroot() as handling symlinks can be tricky in the SFTP
callbacks.
!!! WARNING / ACHTUNG !!!
*/
#include "config.h"

View File

@@ -10,6 +10,23 @@ allowed to cut-and-paste working code from this file to any license of
program.
The goal is to show the API in action. It's not a reference on how terminal
clients must be made or how a client should react.
!!! WARNING / ACHTUNG !!!
This is not a production-ready SSH server implementation. While it demonstrates
how an SSH server can be implemented, it lacks many steps during
the authentication and session establishment!
It allows to log in any user with hardcoded credentials below or with public
key provided from authorized keys file.
The resulting session keeps running under original user who runs the example
server and therefore it retains the same permissions.
Real-world servers should at very least switch the user to unprivileged one
after authentication using setuid().
!!! WARNING / ACHTUNG !!!
*/
#include "config.h"

View File

@@ -9,6 +9,23 @@ domain. This does not apply to the rest of the library though, but it is
allowed to cut-and-paste working code from this file to any license of
program.
The goal is to show the API in action.
!!! WARNING / ACHTUNG !!!
This is not a production-ready SSH server implementation. While it demonstrates
how an SSH server can be implemented, it lacks many steps during
the authentication and session establishment!
It allows to log in any user with hardcoded credentials below or with public
key provided from authorized keys file.
The resulting session keeps running under original user who runs the example
server and therefore it retains the same permissions.
Real-world servers should at very least switch the user to unprivileged one
after authentication using setuid().
!!! WARNING / ACHTUNG !!!
*/
#include "config.h"

View File

@@ -57,10 +57,11 @@ enum ssh_bind_config_opcode_e {
BIND_CFG_MAX /* Keep this one last in the list */
};
/* @brief Parse configuration file and set the options to the given ssh_bind
/**
* @brief Parse configuration file and set the options to the given ssh_bind
*
* @params[in] sshbind The ssh_bind context to be configured
* @params[in] filename The path to the configuration file
* @param[in] sshbind The ssh_bind context to be configured
* @param[in] filename The path to the configuration file
*
* @returns 0 on successful parsing the configuration file, -1 on error
*/

View File

@@ -538,12 +538,9 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks;
* automatically passed through.
*
* @param list list of callbacks
*
* @param cbtype type of the callback
*
* @param c callback name
*
* @param va_args parameters to be passed
* @param ... Parameters to be passed to the callback.
*/
#define ssh_callbacks_execute_list(list, cbtype, c, ...) \
do { \
@@ -585,9 +582,18 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks;
_cb = ssh_iterator_value(_cb_type, _cb_i); \
if (ssh_callbacks_exists(_cb, _cb_name))
/** @internal
* @brief Execute the current callback in an ssh_callbacks_iterate() loop.
*
* @param _cb_name The name of the callback field to invoke.
* @param ... Parameters to be passed to the callback.
*/
#define ssh_callbacks_iterate_exec(_cb_name, ...) \
_cb->_cb_name(__VA_ARGS__, _cb->userdata)
/** @internal
* @brief End an ssh_callbacks_iterate() loop.
*/
#define ssh_callbacks_iterate_end() \
} \
} while(0)
@@ -1060,8 +1066,18 @@ LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel,
* @{
*/
/** @brief Callback for thread mutex operations (init, destroy, lock, unlock).
*
* @param lock Pointer to the mutex lock.
*
* @return 0 on success, non-zero on error.
*/
typedef int (*ssh_thread_callback) (void **lock);
/** @brief Callback to retrieve the current thread identifier.
*
* @return The unique identifier of the calling thread.
*/
typedef unsigned long (*ssh_thread_id_callback) (void);
struct ssh_threads_callbacks_struct {
const char *type;
@@ -1172,10 +1188,20 @@ typedef int (*ssh_jump_verify_knownhost_callback)(ssh_session session,
typedef int (*ssh_jump_authenticate_callback)(ssh_session session,
void *userdata);
/**
* @brief Callback collection for managing an SSH proxyjump connection.
*
* Set these callbacks to control knownhost verification and authentication
* on the jump host before the final destination is reached.
*/
struct ssh_jump_callbacks_struct {
/** Userdata passed to each callback. */
void *userdata;
/** Called before connecting to the jump host. */
ssh_jump_before_connection_callback before_connection;
/** Called to verify the jump host's identity. */
ssh_jump_verify_knownhost_callback verify_knownhost;
/** Called to authenticate on the jump host. */
ssh_jump_authenticate_callback authenticate;
};

View File

@@ -70,9 +70,15 @@ struct ssh_iterator {
const void *data;
};
/**
* @brief Holds connection details for an SSH proxyjump host.
*/
struct ssh_jump_info_struct {
/** Hostname or IP address of the jump host. */
char *hostname;
/** Username to authenticate with on the jump host. */
char *username;
/** Port number of the jump host. */
int port;
};

View File

@@ -130,14 +130,15 @@ extern "C" {
/* SSH Key Functions */
void ssh_key_clean (ssh_key key);
const char *
ssh_key_get_signature_algorithm(ssh_session session,
enum ssh_keytypes_e type);
enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name);
const char *ssh_key_get_signature_algorithm(ssh_session session,
enum ssh_keytypes_e type);
enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type);
enum ssh_digest_e ssh_key_type_to_hash(ssh_session session,
enum ssh_keytypes_e type);
enum ssh_digest_e ssh_key_hash_from_name(const char *name);
int ssh_key_type_and_hash_from_signature_name(const char *name,
enum ssh_keytypes_e *type,
enum ssh_digest_e *hash_type);
#define is_ecdsa_key_type(t) \
((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521)

View File

@@ -35,6 +35,11 @@
extern "C" {
#endif
/**
* @brief Options for configuring an SSH server bind session.
*
* Used with ssh_bind_options_set() to configure server-side options.
*/
enum ssh_bind_options_e {
SSH_BIND_OPTIONS_BINDADDR,
SSH_BIND_OPTIONS_BINDPORT,

View File

@@ -64,6 +64,7 @@ extern "C" {
#endif /* _MSC_VER */
#endif /* _WIN32 */
/** @brief The SFTP protocol version implemented by this library. */
#define LIBSFTP_VERSION 3
typedef struct sftp_attributes_struct* sftp_attributes;
@@ -90,10 +91,76 @@ typedef struct sftp_request_queue_struct* sftp_request_queue;
* @see sftp_free
*/
typedef struct sftp_session_struct* sftp_session;
/**
* @brief Handle for an SFTP status message received from the server.
*
* This type represents a status response returned by the SFTP server in
* reply to a client request. It carries a numeric status code and an optional
* human-readable error message. This type is used internally by libssh and
* is not part of the public API.
*
* @see sftp_status_message_struct
*/
typedef struct sftp_status_message_struct* sftp_status_message;
/**
* @brief Handle for SFTP file system statistics.
*
* This type represents file system statistics as reported by the SFTP server,
* analogous to the POSIX @c statvfs structure. It is obtained via
* sftp_statvfs() or sftp_fstatvfs() and must be freed with
* sftp_statvfs_free().
*
* @see sftp_statvfs_struct
* @see sftp_statvfs
* @see sftp_fstatvfs
* @see sftp_statvfs_free
*/
typedef struct sftp_statvfs_struct* sftp_statvfs_t;
/**
* @brief Handle for SFTP server limits information.
*
* This type represents the server-reported SFTP limits such as the maximum
* packet, read, and write lengths and the maximum number of open handles.
* It is obtained via sftp_limits() and must be freed with sftp_limits_free().
*
* @see sftp_limits_struct
* @see sftp_limits
* @see sftp_limits_free
*/
typedef struct sftp_limits_struct* sftp_limits_t;
/**
* @brief Handle for an asynchronous SFTP I/O operation.
*
* This type represents an in-flight asynchronous SFTP read or write request.
* It is allocated by sftp_aio_begin_read() or sftp_aio_begin_write() and
* consumed by the corresponding sftp_aio_wait_read() or sftp_aio_wait_write()
* call. If the wait call is not reached, the handle must be freed explicitly
* with sftp_aio_free() to avoid memory leaks.
*
* @see sftp_aio_begin_read
* @see sftp_aio_wait_read
* @see sftp_aio_begin_write
* @see sftp_aio_wait_write
* @see sftp_aio_free
*/
typedef struct sftp_aio_struct* sftp_aio;
/**
* @brief Handle for an SFTP name-to-id mapping.
*
* This type stores a mapping between numeric user or group IDs and their
* corresponding names. It is allocated via sftp_name_id_map_new(), populated
* by sftp_get_users_groups_by_id(), and freed with sftp_name_id_map_free().
*
* @see sftp_name_id_map_struct
* @see sftp_name_id_map_new
* @see sftp_get_users_groups_by_id
* @see sftp_name_id_map_free
*/
typedef struct sftp_name_id_map_struct *sftp_name_id_map;
struct sftp_session_struct {
@@ -392,7 +459,6 @@ LIBSSH_API sftp_session sftp_new(ssh_session session);
*/
LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel);
/**
* @brief Close and deallocate a sftp session.
*
@@ -1582,7 +1648,9 @@ LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle);
#define SFTP_EXTENDED SSH_FXP_EXTENDED
/* openssh flags */
/** @brief statvfs flag: file system is mounted read-only. */
#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */
/** @brief statvfs flag: file system does not support setuid/setgid. */
#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */
#ifdef __cplusplus

View File

@@ -43,17 +43,35 @@ extern "C" {
* @{
*/
/**
* @brief Macro to declare an SFTP message callback function.
*
* @param name The name of the callback function to declare.
*/
#define SSH_SFTP_CALLBACK(name) \
static int name(sftp_client_message message)
/**
* @brief Callback for handling SFTP client messages.
*
* @param message The SFTP client message to handle.
*
* @return 0 on success, -1 on error.
*/
typedef int (*sftp_server_message_callback)(sftp_client_message message);
/**
* @brief Maps an SFTP message type to its handler callback.
*/
struct sftp_message_handler
{
/** The name of the SFTP operation (e.g. "read", "write"). */
const char *name;
/** The extended operation name for SSH_FXP_EXTENDED requests, or NULL. */
const char *extended_name;
/** The SFTP message type code (e.g. SSH_FXP_READ). */
uint8_t type;
/** The callback function to invoke for this message type. */
sftp_server_message_callback cb;
};
@@ -61,6 +79,7 @@ LIBSSH_API int sftp_channel_default_subsystem_request(ssh_session session,
ssh_channel channel,
const char *subsystem,
void *userdata);
LIBSSH_API int sftp_channel_default_data_callback(ssh_session session,
ssh_channel channel,
void *data,

View File

@@ -109,6 +109,11 @@ static void buffer_verify(ssh_buffer buf)
}
#else
/** @internal
* @brief No-op stub for buffer_verify when debug checks are disabled.
*
* @param x The buffer to verify (ignored).
*/
#define buffer_verify(x)
#endif
@@ -964,9 +969,12 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer,
/** @internal
* @brief Add multiple values in a buffer on a single function call
*
* @param[in] buffer The buffer to add to
* @param[in] format A format string of arguments.
* @param[in] argc Number of arguments passed after format.
* @param[in] ap A va_list of arguments.
*
* @returns SSH_OK on success
* SSH_ERROR on error
* @see ssh_buffer_add_format() for format list values.
@@ -1112,6 +1120,9 @@ ssh_buffer_pack_va(struct ssh_buffer_struct *buffer,
* 'P': size_t, void * (len of data, pointer to data)
* only pushes data.
* 'B': bignum (pushed as SSH string)
* @param[in] argc Number of arguments passed after format.
* @param[in] ... Arguments as described by the format string.
*
* @returns SSH_OK on success
* SSH_ERROR on error
* @warning when using 'P' with a constant size (e.g. 8), do not
@@ -1148,7 +1159,9 @@ int _ssh_buffer_pack(struct ssh_buffer_struct *buffer,
* @brief Get multiple values from a buffer on a single function call
* @param[in] buffer The buffer to get from
* @param[in] format A format string of arguments.
* @param[in] argc Number of arguments passed in the va_list.
* @param[in] ap A va_list of arguments.
*
* @returns SSH_OK on success
* SSH_ERROR on error
* @see ssh_buffer_get_format() for format list values.
@@ -1411,6 +1424,9 @@ cleanup:
* 'P': size_t, void ** (len of data, pointer to data)
* only pulls data.
* 'B': bignum * (pulled as SSH string)
* @param[in] argc Number of arguments passed after format.
* @param[in] ... Arguments as described by the format string.
*
* @returns SSH_OK on success
* SSH_ERROR on error
* @warning when using 'P' with a constant size (e.g. 8), do not

View File

@@ -1738,7 +1738,7 @@ int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len)
*/
int ssh_channel_is_open(ssh_channel channel)
{
if (channel == NULL) {
if (channel == NULL || channel->session == NULL) {
return 0;
}
return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0);

View File

@@ -1684,11 +1684,12 @@ int ssh_config_parse_line_cli(ssh_session session, const char *line)
true);
}
/* @brief Parse configuration from a file pointer
/**
* @brief Parse configuration from a file pointer
*
* @params[in] session The ssh session
* @params[in] fp A valid file pointer
* @params[in] global Whether the config is global or not
* @param[in] session The ssh session
* @param[in] fp A valid file pointer
* @param[in] global Whether the config is global or not
*
* @returns 0 on successful parsing the configuration file, -1 on error
*/
@@ -1710,10 +1711,11 @@ int ssh_config_parse(ssh_session session, FILE *fp, bool global)
return 0;
}
/* @brief Parse configuration file and set the options to the given session
/**
* @brief Parse configuration file and set the options to the given session
*
* @params[in] session The ssh session
* @params[in] filename The path to the ssh configuration file
* @param[in] session The ssh session
* @param[in] filename The path to the ssh configuration file
*
* @returns 0 on successful parsing the configuration file, -1 on error
*/
@@ -1748,10 +1750,11 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
return rv;
}
/* @brief Parse configuration string and set the options to the given session
/**
* @brief Parse configuration string and set the options to the given session
*
* @params[in] session The ssh session
* @params[in] input Null terminated string containing the configuration
* @param[in] session The ssh session
* @param[in] input Null terminated string containing the configuration
*
* @returns SSH_OK on successful parsing the configuration string,
* SSH_ERROR on error

View File

@@ -43,11 +43,9 @@
* @brief Registers an error with a description.
*
* @param error The place to store the error.
*
* @param code The class of error.
*
* @param function The name of the calling function.
* @param descr The description, which can be a format string.
*
* @param ... The arguments for the format string.
*/
void _ssh_set_error(void *error,
@@ -76,6 +74,7 @@ void _ssh_set_error(void *error,
* @brief Registers an out of memory error
*
* @param error The place to store the error.
* @param function The name of the calling function.
*
*/
void _ssh_set_error_oom(void *error, const char *function)

View File

@@ -770,6 +770,16 @@ ssh_string ssh_get_pubkey(ssh_session session)
****************************************************************************/
#ifdef WITH_SERVER
/**
* @brief Accept an incoming SSH connection on a bind socket.
*
* @deprecated Use ssh_bind_accept() instead.
*
* @param session The SSH session to accept the connection on.
*
* @return SSH_OK on success, SSH_ERROR on error.
*/
int ssh_accept(ssh_session session) {
return ssh_handle_key_exchange(session);
}

View File

@@ -109,6 +109,16 @@ static void ssh_log_custom(ssh_logging_callback log_fn,
log_fn(verbosity, function, buf, ssh_get_log_userdata());
}
/** @internal
* @brief Dispatch a pre-formatted log message to the active logging backend.
*
* If a custom logging callback is registered, the message is passed to it.
* Otherwise, the message is written to stderr.
*
* @param verbosity The verbosity level of the message.
* @param function The name of the calling function.
* @param buffer The already-formatted log message string.
*/
void ssh_log_function(int verbosity,
const char *function,
const char *buffer)
@@ -123,6 +133,15 @@ void ssh_log_function(int verbosity,
ssh_log_stderr(verbosity, function, buffer);
}
/** @internal
* @brief Format a log message from a va_list and dispatch it for logging.
*
* @param verbosity The verbosity level of the message.
* @param function The name of the calling function.
* @param format A printf-style format string.
* @param va Pointer to the variable argument list
* for the format string.
*/
void ssh_vlog(int verbosity,
const char *function,
const char *format,
@@ -134,6 +153,15 @@ void ssh_vlog(int verbosity,
ssh_log_function(verbosity, function, buffer);
}
/** @internal
* @brief Log a message if the given verbosity does not
* exceed the global log level.
*
* @param verbosity The verbosity level of the message.
* @param function The name of the calling function.
* @param format A printf-style format string.
* @param ... Additional arguments corresponding to the format string.
*/
void _ssh_log(int verbosity,
const char *function,
const char *format, ...)
@@ -149,6 +177,15 @@ void _ssh_log(int verbosity,
/* LEGACY */
/** @brief Log a message using the verbosity level of the given session.
*
* @deprecated Use the SSH_LOG() macro instead.
*
* @param session The SSH session whose verbosity level is checked.
* @param verbosity The verbosity level of the message.
* @param format A printf-style format string.
* @param ... Arguments as described by the format string.
*/
void ssh_log(ssh_session session,
int verbosity,
const char *format, ...)
@@ -164,9 +201,14 @@ void ssh_log(ssh_session session,
/** @internal
* @brief log a SSH event with a common pointer
* @param common The SSH/bind session.
* @param verbosity The verbosity of the event.
* @param format The format string of the log entry.
*
* Works for both ssh_session and ssh_bind as both embed ssh_common_struct.
*
* @param common The SSH/bind session.
* @param verbosity The verbosity of the event.
* @param function The name of the calling function.
* @param format The format string of the log entry.
* @param ... Additional arguments corresponding to the format string.
*/
void ssh_log_common(struct ssh_common_struct *common,
int verbosity,
@@ -221,6 +263,9 @@ int ssh_set_log_callback(ssh_logging_callback cb) {
return SSH_OK;
}
/** @internal
* @brief Clear the thread-local logging callback, reverting to stderr logging.
*/
void
_ssh_reset_log_cb(void)
{

View File

@@ -127,7 +127,13 @@ static char *ssh_get_user_home_dir_internal(void)
return NULL;
}
/* we have read access on file */
/** @internal
* @brief Check whether the current process has read access to a file.
*
* @param[in] file Path to the file to check.
*
* @return 1 if the file is readable, 0 otherwise.
*/
int ssh_file_readaccess_ok(const char *file)
{
if (_access(file, 4) < 0) {
@@ -208,11 +214,11 @@ struct tm *ssh_localtime(const time_t *timer, struct tm *result)
return result;
}
/**
* @brief Get username from the calling process.
/** @internal
* @brief Get the username of the currently running process.
*
* @return An allocated string with the user on success, NULL on failure. The
* caller is responsible for freeing returned string.
* @return A newly allocated string with the username, or NULL on error.
* The caller is responsible for freeing it.
*/
char *ssh_get_local_username(void)
{
@@ -240,6 +246,13 @@ char *ssh_get_local_username(void)
return NULL;
}
/** @internal
* @brief Check whether a string is a valid IPv4 address.
*
* @param[in] str The string to check.
*
* @return 1 if the string is a valid IPv4 address, 0 otherwise.
*/
int ssh_is_ipaddr_v4(const char *str)
{
struct sockaddr_storage ss;
@@ -263,6 +276,13 @@ int ssh_is_ipaddr_v4(const char *str)
return 0;
}
/** @internal
* @brief Check whether a string is a valid IPv4 or IPv6 address.
*
* @param[in] str The string to check.
*
* @return 1 if valid IP address, 0 if not, -1 on memory error.
*/
int ssh_is_ipaddr(const char *str)
{
int rc = SOCKET_ERROR;
@@ -328,7 +348,13 @@ static char *ssh_get_user_home_dir_internal(void)
return szPath;
}
/* we have read access on file */
/** @internal
* @brief Check whether the current process has read access to a file.
*
* @param[in] file Path to the file to check.
*
* @return 1 if the file is readable, 0 otherwise.
*/
int ssh_file_readaccess_ok(const char *file)
{
if (access(file, R_OK) < 0) {
@@ -363,11 +389,11 @@ int ssh_dir_writeable(const char *path)
return 0;
}
/**
* @brief Get username from the calling process.
/** @internal
* @brief Get the username of the currently running process.
*
* @return An allocated string with the name on success, NULL on failure. The
* caller is responsible for freeing returned string.
* @return A newly allocated string with the username, or NULL on error.
* The caller is responsible for freeing it.
*/
char *ssh_get_local_username(void)
{
@@ -393,6 +419,13 @@ char *ssh_get_local_username(void)
return name;
}
/** @internal
* @brief Check whether a string is a valid IPv4 address.
*
* @param[in] str The string to check.
*
* @return 1 if the string is a valid IPv4 address, 0 otherwise.
*/
int ssh_is_ipaddr_v4(const char *str)
{
int rc = -1;
@@ -406,6 +439,13 @@ int ssh_is_ipaddr_v4(const char *str)
return 0;
}
/** @internal
* @brief Check whether a string is a valid IPv4 or IPv6 address.
*
* @param[in] str The string to check.
*
* @return 1 if valid IP address, 0 if not, -1 on memory error.
*/
int ssh_is_ipaddr(const char *str)
{
int rc = -1;
@@ -440,6 +480,17 @@ int ssh_is_ipaddr(const char *str)
#endif /* _WIN32 */
/** @internal
* @brief Get the home directory of the current user.
*
* If a session is provided and a cached value exists, it is returned directly.
* Otherwise the home directory is looked up and cached in the session.
*
* @param[in] session The SSH session to cache the result in, or NULL.
*
* @return A newly allocated string with the home directory path, or NULL
* on error. The caller is responsible for freeing it.
*/
char *ssh_get_user_home_dir(ssh_session session)
{
char *szPath = NULL;
@@ -463,6 +514,14 @@ char *ssh_get_user_home_dir(ssh_session session)
return szPath;
}
/** @internal
* @brief Convert a string to lowercase.
*
* @param[in] str The string to convert.
*
* @return A newly allocated lowercase copy of the string, or NULL on error.
* The caller is responsible for freeing it.
*/
char *ssh_lowercase(const char* str)
{
char *new = NULL, *p = NULL;
@@ -483,6 +542,15 @@ char *ssh_lowercase(const char* str)
return new;
}
/** @internal
* @brief Format a host and port into a "[host]:port" string.
*
* @param[in] host The hostname or IP address.
* @param[in] port The port number.
*
* @return A newly allocated string of the form "[host]:port", or NULL
* on error. The caller is responsible for freeing it.
*/
char *ssh_hostport(const char *host, int port)
{
char *dest = NULL;
@@ -788,6 +856,11 @@ const char *ssh_version(int req_version)
return NULL;
}
/** @internal
* @brief Create a new empty linked list.
*
* @return A newly allocated ssh_list, or NULL on memory error.
*/
struct ssh_list *ssh_list_new(void)
{
struct ssh_list *ret = malloc(sizeof(struct ssh_list));
@@ -798,6 +871,13 @@ struct ssh_list *ssh_list_new(void)
return ret;
}
/** @internal
* @brief Free a linked list and all its iterator nodes.
*
* The data pointed to by each node is not freed.
*
* @param[in] list The list to free.
*/
void ssh_list_free(struct ssh_list *list)
{
struct ssh_iterator *ptr = NULL, *next = NULL;
@@ -812,6 +892,13 @@ void ssh_list_free(struct ssh_list *list)
SAFE_FREE(list);
}
/** @internal
* @brief Get the first iterator of a linked list.
*
* @param[in] list The list to iterate.
*
* @return Pointer to the first iterator, or NULL if the list is empty.
*/
struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list)
{
if (!list)
@@ -819,6 +906,14 @@ struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list)
return list->root;
}
/** @internal
* @brief Find the iterator pointing to a specific value in the list.
*
* @param[in] list The list to search.
* @param[in] value The data pointer to find.
*
* @return The iterator pointing to the value, or NULL if not found.
*/
struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value)
{
struct ssh_iterator *it = NULL;
@@ -894,6 +989,14 @@ int ssh_list_append(struct ssh_list *list, const void *data)
return SSH_OK;
}
/** @internal
* @brief Prepend an element at the beginning of the list.
*
* @param[in] list The list to prepend to.
* @param[in] data The element to prepend.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_list_prepend(struct ssh_list *list, const void *data)
{
struct ssh_iterator *it = NULL;
@@ -919,6 +1022,12 @@ int ssh_list_prepend(struct ssh_list *list, const void *data)
return SSH_OK;
}
/** @internal
* @brief Remove an element from the list by its iterator.
*
* @param[in] list The list to remove from.
* @param[in] iterator The iterator pointing to the element to remove.
*/
void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator)
{
struct ssh_iterator *ptr = NULL, *prev = NULL;
@@ -1257,6 +1366,12 @@ char *ssh_path_expand_tilde(const char *d)
return r;
}
/** @internal
* @brief Get the hostname of the local machine.
*
* @return A newly allocated string with the hostname, or NULL on error.
* The caller is responsible for freeing it.
*/
char *ssh_get_local_hostname(void)
{
char host[NI_MAXHOST] = {0};
@@ -1359,6 +1474,7 @@ err:
/** @internal
* @brief expands a string in function of session options
*
* @param[in] session The SSH session providing option values for expansion.
* @param[in] s Format string to expand. Known parameters:
* - %d user home directory (~)
* - %h target host name
@@ -1791,6 +1907,15 @@ void burn_free(void *ptr, size_t len)
}
#if !defined(HAVE_STRNDUP)
/** @internal
* @brief Compatibility implementation of strndup for systems that lack it.
*
* @param[in] s The string to duplicate.
* @param[in] n Maximum number of characters to copy.
*
* @return A newly allocated null-terminated string, or NULL on error.
*/
char *strndup(const char *s, size_t n)
{
char *x = NULL;
@@ -1811,7 +1936,11 @@ char *strndup(const char *s, size_t n)
}
#endif /* ! HAVE_STRNDUP */
/* Increment 64b integer in network byte order */
/** @internal
* @brief Increment a 64-bit counter stored in network byte order.
*
* @param[in,out] counter Pointer to an 8-byte buffer holding the counter.
*/
void
uint64_inc(unsigned char *counter)
{

View File

@@ -288,6 +288,20 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
return 0;
}
/** @internal
* @brief Set a key exchange algorithm list option on the session.
*
* Supports prefix modifiers: '+' to append, '-' to remove, '^' to prepend
* to the default algorithm list.
*
* @param[in] session The SSH session.
* @param[in] algo The algorithm type to configure.
* @param[in] list The algorithm list string.
* @param[out] place Pointer to the string to store
* the resulting algorithm list.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_options_set_algo(ssh_session session,
enum ssh_kex_types_e algo,
const char *list,
@@ -2081,6 +2095,16 @@ out:
return r;
}
/** @internal
* @brief Apply default values for unset session options.
*
* Sets default SSH directory and username if not already configured,
* and resolves any remaining option expansions.
*
* @param[in] session The SSH session to apply defaults to.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_options_apply(ssh_session session)
{
char *tmp = NULL;

180
src/pki.c
View File

@@ -449,40 +449,137 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) {
/* We should never reach this */
return NULL;
}
enum ssh_digest_e ssh_key_hash_from_name(const char *name)
/**
* @brief Convert a signature algorithm name to a key type and hash type.
*
* Looks up the given signature algorithm name and returns both the
* corresponding key type and digest algorithm in a single call,
* avoiding double string comparisons on the same input.
*
* @param[in] name The signature algorithm name to convert (e.g.
* "ssh-rsa", "rsa-sha2-256", "ecdsa-sha2-nistp256").
*
* @param[out] type A pointer to store the resulting key type.
*
* @param[out] hash_type A pointer to store the resulting hash/digest type.
*
* @return SSH_OK on success, SSH_ERROR if name is NULL or
* unknown.
*/
int ssh_key_type_and_hash_from_signature_name(const char *name,
enum ssh_keytypes_e *type,
enum ssh_digest_e *hash_type)
{
if (name == NULL) {
/* TODO we should rather fail */
return SSH_DIGEST_AUTO;
size_t len;
if (name == NULL || type == NULL || hash_type == NULL) {
return SSH_ERROR;
}
if (strcmp(name, "ssh-rsa") == 0) {
return SSH_DIGEST_SHA1;
} else if (strcmp(name, "rsa-sha2-256") == 0) {
return SSH_DIGEST_SHA256;
} else if (strcmp(name, "rsa-sha2-512") == 0) {
return SSH_DIGEST_SHA512;
} else if (strcmp(name, "ecdsa-sha2-nistp256") == 0) {
return SSH_DIGEST_SHA256;
} else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) {
return SSH_DIGEST_SHA384;
} else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) {
return SSH_DIGEST_SHA512;
} else if (strcmp(name, "ssh-ed25519") == 0) {
return SSH_DIGEST_AUTO;
} else if (strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) {
return SSH_DIGEST_SHA256;
} else if (strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) {
return SSH_DIGEST_AUTO;
len = strlen(name);
if (len == 7 && strcmp(name, "ssh-rsa") == 0) {
*type = SSH_KEYTYPE_RSA;
*hash_type = SSH_DIGEST_SHA1;
return SSH_OK;
}
if (len == 11 && strcmp(name, "ssh-ed25519") == 0) {
*type = SSH_KEYTYPE_ED25519;
*hash_type = SSH_DIGEST_AUTO;
return SSH_OK;
}
if (len == 12) {
if (strcmp(name, "rsa-sha2-256") == 0) {
*type = SSH_KEYTYPE_RSA;
*hash_type = SSH_DIGEST_SHA256;
return SSH_OK;
}
if (strcmp(name, "rsa-sha2-512") == 0) {
*type = SSH_KEYTYPE_RSA;
*hash_type = SSH_DIGEST_SHA512;
return SSH_OK;
}
}
if (len == 19) {
if (strcmp(name, "ecdsa-sha2-nistp256") == 0) {
*type = SSH_KEYTYPE_ECDSA_P256;
*hash_type = SSH_DIGEST_SHA256;
return SSH_OK;
}
if (strcmp(name, "ecdsa-sha2-nistp384") == 0) {
*type = SSH_KEYTYPE_ECDSA_P384;
*hash_type = SSH_DIGEST_SHA384;
return SSH_OK;
}
if (strcmp(name, "ecdsa-sha2-nistp521") == 0) {
*type = SSH_KEYTYPE_ECDSA_P521;
*hash_type = SSH_DIGEST_SHA512;
return SSH_OK;
}
}
if (len == 26 && strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) {
*type = SSH_KEYTYPE_SK_ED25519;
*hash_type = SSH_DIGEST_AUTO;
return SSH_OK;
}
if (len == 28 && strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_RSA_CERT01;
*hash_type = SSH_DIGEST_SHA1;
return SSH_OK;
}
if (len == 32 && strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_ED25519_CERT01;
*hash_type = SSH_DIGEST_AUTO;
return SSH_OK;
}
if (len == 33) {
if (strcmp(name, "rsa-sha2-256-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_RSA_CERT01;
*hash_type = SSH_DIGEST_SHA256;
return SSH_OK;
}
if (strcmp(name, "rsa-sha2-512-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_RSA_CERT01;
*hash_type = SSH_DIGEST_SHA512;
return SSH_OK;
}
}
if (len == 34 && strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) {
*type = SSH_KEYTYPE_SK_ECDSA;
*hash_type = SSH_DIGEST_SHA256;
return SSH_OK;
}
if (len == 40) {
if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_ECDSA_P256_CERT01;
*hash_type = SSH_DIGEST_SHA256;
return SSH_OK;
}
if (strcmp(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_ECDSA_P384_CERT01;
*hash_type = SSH_DIGEST_SHA384;
return SSH_OK;
}
if (strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) {
*type = SSH_KEYTYPE_ECDSA_P521_CERT01;
*hash_type = SSH_DIGEST_SHA512;
return SSH_OK;
}
}
SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name);
/* TODO we should rather fail */
return SSH_DIGEST_AUTO;
return SSH_ERROR;
}
/**
* @brief Checks the given key against the configured allowed
* public key algorithm types
@@ -700,27 +797,6 @@ ssh_key_get_signature_algorithm(ssh_session session,
return ssh_key_signature_to_char(type, hash_type);
}
/**
* @brief Convert a ssh key algorithm name to a ssh key algorithm type.
*
* @param[in] name The name to convert.
*
* @return The enum ssh key algorithm type.
*/
enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) {
if (name == NULL) {
return SSH_KEYTYPE_UNKNOWN;
}
if ((strcmp(name, "rsa-sha2-256") == 0) ||
(strcmp(name, "rsa-sha2-512") == 0)) {
return SSH_KEYTYPE_RSA;
}
/* Otherwise the key type matches the signature type */
return ssh_key_type_from_name(name);
}
/**
* @brief Convert a ssh key name to a ssh key type.
*
@@ -2899,8 +2975,12 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob,
}
alg = ssh_string_get_char(algorithm);
type = ssh_key_type_from_signature_name(alg);
hash_type = ssh_key_hash_from_name(alg);
rc = ssh_key_type_and_hash_from_signature_name(alg, &type, &hash_type);
if (rc != SSH_OK) {
SSH_BUFFER_FREE(buf);
SSH_STRING_FREE(algorithm);
return SSH_ERROR;
}
SSH_STRING_FREE(algorithm);
blob = ssh_buffer_get_ssh_string(buf);

View File

@@ -84,16 +84,33 @@ struct ssh_poll_ctx_struct {
#ifdef HAVE_POLL
#include <poll.h>
/** @internal
* @brief Initialize the poll subsystem. No-op when native poll is available.
*/
void ssh_poll_init(void)
{
return;
}
/** @internal
* @brief Clean up the poll subsystem. No-op when native poll is available.
*/
void ssh_poll_cleanup(void)
{
return;
}
/** @internal
* @brief Wait for events on a set of file descriptors.
*
* @param fds Array of pollfd structures specifying the file descriptors.
* @param nfds Number of file descriptors in the array.
* @param timeout Timeout in milliseconds, `SSH_TIMEOUT_INFINITE`
* to block indefinitely.
*
* @return Number of file descriptors with events, 0 on timeout,
* -1 on error.
*/
int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout)
{
return poll((struct pollfd *)fds, nfds, timeout);
@@ -321,16 +338,33 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout)
return rc;
}
/** @internal
* @brief Initialize the poll subsystem using the BSD poll emulation.
*/
void ssh_poll_init(void)
{
ssh_poll_emu = bsd_poll;
}
/** @internal
* @brief Clean up the poll subsystem, resetting to the BSD poll emulation.
*/
void ssh_poll_cleanup(void)
{
ssh_poll_emu = bsd_poll;
}
/** @internal
* @brief Wait for events on a set of file descriptors.
*
* @param fds Array of pollfd structures specifying the file descriptors.
* @param nfds Number of file descriptors in the array.
* @param timeout Timeout in milliseconds, `SSH_TIMEOUT_INFINITE`
* to block indefinitely.
*
* @return Number of file descriptors with events, 0 on timeout,
* -1 on error.
*/
int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout)
{
return (ssh_poll_emu)(fds, nfds, timeout);

View File

@@ -1010,7 +1010,9 @@ int ssh_get_version(ssh_session session) {
/**
* @internal
* @brief Callback to be called when the socket received an exception code.
* @param user is a pointer to session
* @param code The exception code from the socket layer.
* @param errno_code The errno value associated with the exception.
* @param user Pointer to the SSH session.
*/
void ssh_socket_exception_callback(int code, int errno_code, void *user){
ssh_session session = (ssh_session)user;

View File

@@ -24,11 +24,12 @@
#include "config.h"
#ifndef _WIN32
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <grp.h>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/statvfs.h>
#endif
@@ -239,9 +240,7 @@ sftp_make_client_message(sftp_session sftp, sftp_packet packet)
}
break;
case SSH_FXP_EXTENDED:
rc = ssh_buffer_unpack(payload,
"s",
&msg->submessage);
rc = ssh_buffer_unpack(payload, "s", &msg->submessage);
if (rc != SSH_OK) {
goto error;
}
@@ -256,9 +255,13 @@ sftp_make_client_message(sftp_session sftp, sftp_packet packet)
goto error;
}
} else if (strcmp(msg->submessage, "statvfs@openssh.com") == 0 ){
rc = ssh_buffer_unpack(payload,
"s",
&msg->filename);
rc = ssh_buffer_unpack(payload, "s", &msg->filename);
if (rc != SSH_OK) {
goto error;
}
} else if (strcmp(msg->submessage,
"users-groups-by-id@openssh.com") == 0) {
rc = ssh_buffer_unpack(payload, "SS", &msg->data, &msg->handle);
if (rc != SSH_OK) {
goto error;
}
@@ -834,14 +837,17 @@ int sftp_reply_version(sftp_client_message client_msg)
return -1;
}
rc = ssh_buffer_pack(reply, "dssssss",
rc = ssh_buffer_pack(reply,
"dssssssss",
LIBSFTP_VERSION,
"posix-rename@openssh.com",
"1",
"hardlink@openssh.com",
"1",
"statvfs@openssh.com",
"2");
"2",
"users-groups-by-id@openssh.com",
"1");
if (rc != SSH_OK) {
ssh_set_error_oom(session);
SSH_BUFFER_FREE(reply);
@@ -948,6 +954,14 @@ void *sftp_handle(sftp_session sftp, ssh_string handle)
return sftp->handles[val];
}
/**
* @brief Remove an SFTP file or directory handle from the session.
*
* @param sftp The SFTP session.
* @param handle The handle to remove.
*
* @see sftp_handle_alloc()
*/
void sftp_handle_remove(sftp_session sftp, void *handle)
{
int i;
@@ -1059,6 +1073,352 @@ struct sftp_handle
char *name;
};
/**
* @internal
*
* @brief Parse a blob of IDs into a sftp_name_id_map.
*
* This function extracts numeric IDs from the binary blob and populates
* the 'ids' array of the map. Note that each element of the 'names'
* array in the map is initialized to NULL by this function.
*
* @param[in] ids_blob The binary string blob containing uint32_t IDs.
*
* @return A newly allocated sftp_name_id_map on success, or NULL on error.
*/
static sftp_name_id_map sftp_name_id_map_from_ids_blob(ssh_string ids_blob)
{
sftp_name_id_map map = NULL;
ssh_buffer buf = NULL;
size_t len;
uint32_t count, i;
int rc;
if (ids_blob == NULL) {
SSH_LOG(SSH_LOG_WARNING, "IDs blob is NULL");
return NULL;
}
len = ssh_string_len(ids_blob);
if (len % sizeof(uint32_t) != 0) {
SSH_LOG(SSH_LOG_WARNING,
"IDs blob length is not a multiple of 4 bytes");
return NULL;
}
count = len / sizeof(uint32_t);
map = sftp_name_id_map_new(count);
if (map == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate sftp_name_id_map");
return NULL;
}
buf = ssh_buffer_new();
if (buf == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate ssh_buffer");
sftp_name_id_map_free(map);
return NULL;
}
rc = ssh_buffer_add_data(buf, ssh_string_data(ids_blob), len);
if (rc < 0) {
SSH_LOG(SSH_LOG_WARNING, "Failed to copy blob data to buffer");
SSH_BUFFER_FREE(buf);
sftp_name_id_map_free(map);
return NULL;
}
for (i = 0; i < count; i++) {
uint32_t val;
rc = ssh_buffer_unpack(buf, "d", &val);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to unpack ID from buffer");
SSH_BUFFER_FREE(buf);
sftp_name_id_map_free(map);
return NULL;
}
map->ids[i] = val;
}
SSH_BUFFER_FREE(buf);
return map;
}
/**
* @internal
*
* @brief Fill the names array using UIDs in the map.
*/
static int sftp_fill_names_using_uids(sftp_name_id_map users_map)
{
struct passwd pwd_struct;
struct passwd *pwd_res = NULL;
long pwd_buf_size = -1;
char *pwd_lookup_buf = NULL;
uint32_t i;
int rc = SSH_OK;
if (users_map == NULL) {
return SSH_ERROR;
}
#ifdef _SC_GETPW_R_SIZE_MAX
pwd_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
#endif
if (pwd_buf_size <= 0) {
pwd_buf_size = 16384;
}
pwd_lookup_buf = calloc(1, pwd_buf_size);
if (pwd_lookup_buf == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate pwd lookup buffer");
return SSH_ERROR;
}
for (i = 0; i < users_map->count; i++) {
int ret = getpwuid_r(users_map->ids[i],
&pwd_struct,
pwd_lookup_buf,
pwd_buf_size,
&pwd_res);
SAFE_FREE(users_map->names[i]);
if (ret == 0 && pwd_res != NULL) {
users_map->names[i] = strdup(pwd_res->pw_name);
} else {
users_map->names[i] = strdup("");
}
if (users_map->names[i] == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to allocate memory for username string");
rc = SSH_ERROR;
break;
}
}
SAFE_FREE(pwd_lookup_buf);
return rc;
}
/**
* @internal
*
* @brief Fill the names array using GIDs in the map.
*/
static int sftp_fill_names_using_gids(sftp_name_id_map groups_map)
{
struct group grp_struct;
struct group *grp_res = NULL;
long grp_buf_size = -1;
char *grp_lookup_buf = NULL;
uint32_t i;
int rc = SSH_OK;
if (groups_map == NULL) {
return SSH_ERROR;
}
#ifdef _SC_GETGR_R_SIZE_MAX
grp_buf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
#endif
if (grp_buf_size <= 0) {
grp_buf_size = 16384;
}
grp_lookup_buf = calloc(1, grp_buf_size);
if (grp_lookup_buf == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate grp lookup buffer");
return SSH_ERROR;
}
for (i = 0; i < groups_map->count; i++) {
int ret = getgrgid_r(groups_map->ids[i],
&grp_struct,
grp_lookup_buf,
grp_buf_size,
&grp_res);
SAFE_FREE(groups_map->names[i]);
if (ret == 0 && grp_res != NULL) {
groups_map->names[i] = strdup(grp_res->gr_name);
} else {
groups_map->names[i] = strdup("");
}
if (groups_map->names[i] == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to allocate memory for group name string");
rc = SSH_ERROR;
break;
}
}
SAFE_FREE(grp_lookup_buf);
return rc;
}
/**
* @internal
*
* @brief Pack the resolved names from a map into the output buffer.
*
* This function formats the multiple names according to the
* users-groups-by-id@openssh.com extension specification. Each name in
* the map is individually encoded as an SSH string. The entire concatenated
* sequence of these encoded names is then wrapped and appended to the
* output buffer as one single, large SSH string.
*
* @param[out] out_buffer The destination buffer for the final packed string.
* @param[in] map The map containing the resolved names.
*
* @return SSH_OK on success, or SSH_ERROR on memory allocation failure.
*/
static int sftp_buffer_add_names(ssh_buffer out_buffer, sftp_name_id_map map)
{
ssh_buffer temp_buffer = NULL;
uint32_t i;
int rc;
if (out_buffer == NULL || map == NULL) {
return SSH_ERROR;
}
temp_buffer = ssh_buffer_new();
if (temp_buffer == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to allocate temporary buffer for names");
return SSH_ERROR;
}
for (i = 0; i < map->count; i++) {
const char *name = map->names[i] != NULL ? map->names[i] : "";
rc = ssh_buffer_pack(temp_buffer, "s", name);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to pack name into buffer");
SSH_BUFFER_FREE(temp_buffer);
return SSH_ERROR;
}
}
rc = ssh_buffer_pack(out_buffer,
"dP",
(uint32_t)ssh_buffer_get_len(temp_buffer),
(size_t)ssh_buffer_get_len(temp_buffer),
ssh_buffer_get(temp_buffer));
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to add names string blob to output buffer");
}
SSH_BUFFER_FREE(temp_buffer);
return (rc != SSH_OK) ? SSH_ERROR : SSH_OK;
}
/**
* @internal
*
* @brief Handle users-groups-by-id@openssh.com extension request.
*
* Resolves numeric user IDs (UIDs) and group IDs (GIDs) to their
* corresponding username and group name strings. Returns empty strings
* for IDs that cannot be resolved.
*
* @param[in] client_msg The SFTP client message containing the request.
* client_msg->data contains the UIDs blob.
* client_msg->handle contains the GIDs blob.
*
* @return SSH_OK on success (reply sent to client). On error, an error
* status is sent to the client and SSH_ERROR is returned.
*/
static int process_users_groups_by_id(sftp_client_message client_msg)
{
ssh_buffer out = NULL;
sftp_name_id_map users_map = NULL;
sftp_name_id_map groups_map = NULL;
int rc;
SSH_LOG(SSH_LOG_PROTOCOL, "Processing users-groups-by-id extension");
if (client_msg->data == NULL || client_msg->handle == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing UIDs or GIDs blob");
goto error;
}
users_map = sftp_name_id_map_from_ids_blob(client_msg->data);
if (users_map == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to parse UIDs blob");
goto error;
}
groups_map = sftp_name_id_map_from_ids_blob(client_msg->handle);
if (groups_map == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to parse GIDs blob");
goto error;
}
rc = sftp_fill_names_using_uids(users_map);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to resolve UIDs");
goto error;
}
rc = sftp_fill_names_using_gids(groups_map);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to resolve GIDs");
goto error;
}
out = ssh_buffer_new();
if (out == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate output buffer");
goto error;
}
rc = ssh_buffer_add_u32(out, client_msg->id);
if (rc < 0) {
SSH_LOG(SSH_LOG_WARNING, "Failed to add request ID to buffer");
goto error;
}
rc = sftp_buffer_add_names(out, users_map);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to add users to buffer");
goto error;
}
rc = sftp_buffer_add_names(out, groups_map);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to add groups to buffer");
goto error;
}
rc = sftp_packet_write(client_msg->sftp, SSH_FXP_EXTENDED_REPLY, out);
if (rc < 0) {
SSH_LOG(SSH_LOG_WARNING, "Failed to send extended reply");
goto error;
}
sftp_name_id_map_free(users_map);
sftp_name_id_map_free(groups_map);
SSH_BUFFER_FREE(out);
SSH_LOG(SSH_LOG_PROTOCOL, "Successfully processed request");
return SSH_OK;
error:
sftp_name_id_map_free(users_map);
sftp_name_id_map_free(groups_map);
SSH_BUFFER_FREE(out);
SSH_LOG(SSH_LOG_WARNING, "Sending error response");
sftp_reply_status(client_msg, SSH_FX_FAILURE, "Internal processing error");
return SSH_ERROR;
}
SSH_SFTP_CALLBACK(process_unsupported);
SSH_SFTP_CALLBACK(process_open);
SSH_SFTP_CALLBACK(process_read);
@@ -1103,6 +1463,10 @@ const struct sftp_message_handler message_handlers[] = {
const struct sftp_message_handler extended_handlers[] = {
/* here are some extended type handlers */
{"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs},
{"users-groups-by-id",
"users-groups-by-id@openssh.com",
0,
process_users_groups_by_id},
{NULL, NULL, 0, NULL},
};
@@ -2071,16 +2435,19 @@ sftp_channel_default_subsystem_request(ssh_session session,
}
/**
* @brief Default data callback for sftp server
* @brief Default channel data callback for an SFTP server subsystem.
*
* @param[in] session The ssh session
* @param[in] channel The ssh channel with SFTP session opened
* @param[in] data The data to be processed.
* @param[in] len The length of input data to be processed
* @param[in] is_stderr Unused channel flag for stderr flagging
* @param[in] userdata The pointer to sftp_session
* Processes incoming data on the channel and dispatches it to the SFTP
* server message handler.
*
* @return number of bytes processed, -1 when error occurs.
* @param[in] session The SSH session.
* @param[in] channel The SSH channel carrying the SFTP data.
* @param[in] data The received data buffer.
* @param[in] len The length of the data buffer.
* @param[in] is_stderr Unused; SFTP does not use the stderr stream.
* @param[in] userdata Pointer to the sftp_session handle.
*
* @return Number of bytes processed on success, `SSH_ERROR` on error.
*/
int
sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
@@ -2093,7 +2460,7 @@ sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
sftp_session *sftpp = (sftp_session *)userdata;
sftp_session sftp = NULL;
sftp_client_message msg;
int decode_len;
uint32_t undecoded_len = len;
int rc;
if (sftpp == NULL) {
@@ -2102,17 +2469,25 @@ sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
}
sftp = *sftpp;
decode_len = sftp_decode_channel_data_to_packet(sftp, data, len);
if (decode_len == SSH_ERROR)
return SSH_ERROR;
do {
int decode_len =
sftp_decode_channel_data_to_packet(sftp, data, undecoded_len);
if (decode_len == SSH_ERROR) {
return SSH_ERROR;
}
msg = sftp_get_client_message_from_packet(sftp);
rc = process_client_message(msg);
sftp_client_message_free(msg);
if (rc != SSH_OK)
SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!");
msg = sftp_get_client_message_from_packet(sftp);
rc = process_client_message(msg);
sftp_client_message_free(msg);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!");
}
return decode_len;
undecoded_len -= decode_len;
data = (uint8_t *)data + decode_len;
} while (undecoded_len > 0);
return len;
}
#else
/* Not available on Windows for now */

View File

@@ -65,6 +65,9 @@ struct sockaddr_un {
* @{
*/
/** @internal
* @brief Represents the possible states of an SSH socket connection.
*/
enum ssh_socket_states_e {
SSH_SOCKET_NONE,
SSH_SOCKET_CONNECTING,
@@ -1063,12 +1066,26 @@ int ssh_socket_get_poll_flags(ssh_socket s)
}
#ifdef _WIN32
/** @internal
* @brief Set a socket file descriptor to non-blocking mode.
*
* @param fd The socket file descriptor to configure.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_socket_set_nonblocking(socket_t fd)
{
u_long nonblocking = 1;
return ioctlsocket(fd, FIONBIO, &nonblocking);
}
/** @internal
* @brief Set a socket file descriptor to blocking mode.
*
* @param fd The socket file descriptor to configure.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_socket_set_blocking(socket_t fd)
{
u_long nonblocking = 0;
@@ -1076,11 +1093,25 @@ int ssh_socket_set_blocking(socket_t fd)
}
#else /* _WIN32 */
/** @internal
* @brief Set a socket file descriptor to non-blocking mode.
*
* @param fd The socket file descriptor to configure.
*
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
int ssh_socket_set_nonblocking(socket_t fd)
{
return fcntl(fd, F_SETFL, O_NONBLOCK);
}
/** @internal
* @brief Set a socket file descriptor to blocking mode.
*
* @param fd The socket file descriptor to configure.
*
* @return 0 on success, -1 on error.
*/
int ssh_socket_set_blocking(socket_t fd)
{
return fcntl(fd, F_SETFL, 0);

View File

@@ -62,6 +62,9 @@ int ssh_threads_init(void)
return rc;
}
/** @internal
* @brief Finalize and clean up the threading backend of the crypto libraries.
*/
void ssh_threads_finalize(void)
{
crypto_thread_finalize();
@@ -83,6 +86,12 @@ int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb)
return rc;
}
/** @internal
* @brief Get the type identifier of the currently active threading callbacks.
*
* @return A string identifying the threading backend, or NULL if no
* callbacks are registered.
*/
const char *ssh_threads_get_type(void)
{
if (user_callbacks != NULL) {

View File

@@ -437,6 +437,7 @@ int crypt_set_algorithms_server(ssh_session session){
struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab();
struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab();
int cmp;
int rc;
if (session == NULL) {
return SSH_ERROR;
@@ -580,8 +581,24 @@ int crypt_set_algorithms_server(ssh_session session){
}
method = session->next_crypto->kex_methods[SSH_HOSTKEYS];
session->srv.hostkey = ssh_key_type_from_signature_name(method);
session->srv.hostkey_digest = ssh_key_hash_from_name(method);
/* For GSSAPI key exchange, hostkey algorithm may be "null" */
if (strcmp(method, "null") == 0) {
session->srv.hostkey = SSH_KEYTYPE_UNKNOWN;
session->srv.hostkey_digest = SSH_DIGEST_AUTO;
} else {
rc = ssh_key_type_and_hash_from_signature_name(
method,
&session->srv.hostkey,
&session->srv.hostkey_digest);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"unknown hostkey algorithm %s",
method);
return SSH_ERROR;
}
}
/* setup DH key exchange type */
switch (session->next_crypto->kex_type) {

View File

@@ -13,6 +13,9 @@ macro(fuzzer name)
target_sources(${name} PRIVATE $<TARGET_OBJECTS:ssh_server_mock_obj>)
target_link_libraries(${name} PRIVATE pthread)
endif()
if (WITH_COVERAGE)
append_coverage_compiler_flags_to_target(${name})
endif (WITH_COVERAGE)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set_target_properties(${name}

View File

@@ -69,13 +69,53 @@ struct server_state_st {
struct server_state_st *state);
};
/*TODO: Add documentation */
/**
* @brief Free a server state struct.
*
* Frees all memory inside server_state_st using SAFE_FREE.
*
* @param[in] state The server_state_st struct to free.
*/
void free_server_state(struct server_state_st *state);
/*TODO: Add documentation */
/**
* @brief Run a SSH server based on a server state struct.
*
* Takes a server_state_st struct, validates required fields, and starts
* listening for connections. For each client, it forks a child process
* that calls handle_session. Blocks until SIGTERM is received.
*
* @param[in] state The server configuration struct.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
*
* @note This function blocks until SIGTERM is received.
* @note The state is freed internally; do not use after calling.
* @note If state->log_file is set, stdout/stderr are redirected to it.
*
* @see fork_run_server()
* @see free_server_state()
*/
int run_server(struct server_state_st *state);
/*TODO: Add documentation */
/**
* @brief Fork and run an SSH server in non-blocking mode.
*
* Forks a child process that calls run_server(). The parent returns
* immediately with the child's PID. Designed for tests that need
* a server running in the background.
*
* @param[in] state The server_state_st struct passed to run_server().
* @param[in] free_state Callback to free parent's test data in the child.
* @param[in] userdata Pointer passed to free_state.
*
* @return Child PID on success, -1 on error.
*
* @note The parent should send SIGTERM to the child PID when done.
* @note The state is freed by the child process via run_server().
*
* @see run_server()
*/
pid_t
fork_run_server(struct server_state_st *state,
void (*free_state) (void **userdata),

View File

@@ -26,10 +26,11 @@
#define LIBSSH_STATIC
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_VALGRIND_VALGRIND_H
#include <valgrind/valgrind.h>
@@ -48,6 +49,9 @@
#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts"
#define TEST_INVALID_ID_1 99999
#define TEST_INVALID_ID_2 88888
const char template[] = "temp_dir_XXXXXX";
struct test_server_st {
@@ -71,9 +75,9 @@ static void free_test_server_state(void **state)
static int setup_default_server(void **state)
{
struct torture_state *s;
struct server_state_st *ss;
struct test_server_st *tss;
struct torture_state *s = NULL;
struct server_state_st *ss = NULL;
struct test_server_st *tss = NULL;
char ed25519_hostkey[1024] = {0};
char rsa_hostkey[1024];
@@ -207,9 +211,9 @@ static int setup_default_server(void **state)
static int teardown_default_server(void **state)
{
struct torture_state *s;
struct server_state_st *ss;
struct test_server_st *tss;
struct torture_state *s = NULL;
struct server_state_st *ss = NULL;
struct test_server_st *tss = NULL;
tss = *state;
assert_non_null(tss);
@@ -233,7 +237,7 @@ static int teardown_default_server(void **state)
static int session_setup(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_state *s = NULL;
int verbosity = torture_libssh_verbosity();
char template2[] = "/tmp/ssh_torture_XXXXXX";
char *cwd = NULL;
@@ -335,7 +339,7 @@ static int session_setup_sftp(void **state)
static int session_teardown(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_state *s = NULL;
int rc = 0;
assert_non_null(tss);
@@ -365,10 +369,10 @@ static int session_teardown(void **state)
static void torture_server_establish_sftp(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
ssh_session session;
sftp_session sftp;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
ssh_session session = NULL;
sftp_session sftp = NULL;
int rc;
assert_non_null(tss);
@@ -418,17 +422,17 @@ static void torture_server_establish_sftp(void **state)
static void torture_server_test_sftp_function(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
ssh_session session;
sftp_session sftp;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
ssh_session session = NULL;
sftp_session sftp = NULL;
int rc;
char *rv_str;
sftp_dir dir;
char *rv_str = NULL;
sftp_dir dir = NULL;
char data[65535] = {0};
sftp_file source;
sftp_file to;
sftp_file source = NULL;
sftp_file to = NULL;
int read_len;
int write_len;
@@ -675,10 +679,10 @@ static void torture_server_sftp_open_read_write(void **state)
static void torture_server_sftp_mkdir(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
sftp_session sftp;
ssh_session session;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_session sftp = NULL;
ssh_session session = NULL;
sftp_file new_file = NULL;
char tmp_dir[PATH_MAX] = {0};
char tmp_file[PATH_MAX] = {0};
@@ -750,10 +754,10 @@ static void torture_server_sftp_mkdir(void **state)
static void torture_server_sftp_realpath(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
sftp_session sftp;
ssh_session session;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_session sftp = NULL;
ssh_session session = NULL;
char path[PATH_MAX] = {0};
char exp_path[PATH_MAX] = {0};
char *new_path = NULL;
@@ -799,10 +803,10 @@ static void torture_server_sftp_realpath(void **state)
static void torture_server_sftp_symlink(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
sftp_session sftp;
ssh_session session;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_session sftp = NULL;
ssh_session session = NULL;
sftp_file new_file = NULL;
char tmp_dir[PATH_MAX] = {0};
char tmp_file[PATH_MAX] = {0};
@@ -811,7 +815,7 @@ static void torture_server_sftp_symlink(void **state)
char data[42] = "012345678901234567890123456789012345678901";
char *new_path = NULL;
sftp_attributes a = NULL;
sftp_dir dir;
sftp_dir dir = NULL;
int write_len, num_files = 0;
int rc;
@@ -946,10 +950,10 @@ static void torture_server_sftp_symlink(void **state)
static void torture_server_sftp_extended(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s;
struct torture_sftp *tsftp;
sftp_session sftp;
ssh_session session;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_session sftp = NULL;
ssh_session session = NULL;
sftp_file new_file = NULL;
char tmp_dir[PATH_MAX] = {0};
char tmp_file[PATH_MAX] = {0};
@@ -1095,7 +1099,7 @@ static void torture_server_sftp_handles_exhaustion(void **state)
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
char name[128] = {0};
sftp_file handle, handles[SFTP_HANDLES] = {0};
sftp_file handle = NULL, handles[SFTP_HANDLES] = {0};
sftp_session sftp = NULL;
int rc;
@@ -1300,6 +1304,274 @@ static void torture_server_sftp_payload_overrun(void **state)
free(handle);
}
static void torture_sftp_users_groups_by_id_basic(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_name_id_map users_map = NULL;
sftp_name_id_map groups_map = NULL;
int rc;
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
users_map = sftp_name_id_map_new(2);
assert_non_null(users_map);
groups_map = sftp_name_id_map_new(2);
assert_non_null(groups_map);
users_map->ids[0] = TORTURE_SSH_USER_ID_BOB;
users_map->ids[1] = TORTURE_SSH_USER_ID_ALICE;
groups_map->ids[0] = TORTURE_SSH_GROUP_ID_ROOT;
groups_map->ids[1] = TORTURE_SSH_GROUP_ID_COMMON;
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
assert_int_equal(rc, 0);
assert_non_null(users_map->names[0]);
assert_string_equal(users_map->names[0], TORTURE_SSH_USER_BOB);
assert_non_null(users_map->names[1]);
assert_string_equal(users_map->names[1], TORTURE_SSH_USER_ALICE);
assert_non_null(groups_map->names[0]);
assert_string_equal(groups_map->names[0], TORTURE_SSH_GROUP_ROOT);
assert_non_null(groups_map->names[1]);
assert_string_equal(groups_map->names[1], TORTURE_SSH_GROUP_COMMON);
sftp_name_id_map_free(users_map);
sftp_name_id_map_free(groups_map);
}
static void torture_sftp_users_groups_by_id_unknown(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_name_id_map users_map = NULL;
sftp_name_id_map groups_map = NULL;
int rc;
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
users_map = sftp_name_id_map_new(1);
assert_non_null(users_map);
groups_map = sftp_name_id_map_new(1);
assert_non_null(groups_map);
/* Set User ID and Group ID that do not exist */
users_map->ids[0] = TEST_INVALID_ID_1;
groups_map->ids[0] = TEST_INVALID_ID_1;
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
assert_int_equal(rc, 0);
assert_non_null(users_map->names[0]);
assert_string_equal(users_map->names[0], "");
assert_non_null(groups_map->names[0]);
assert_string_equal(groups_map->names[0], "");
sftp_name_id_map_free(users_map);
sftp_name_id_map_free(groups_map);
}
static void torture_sftp_users_groups_by_id_users_only(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_name_id_map users_map = NULL;
int rc;
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
users_map = sftp_name_id_map_new(1);
assert_non_null(users_map);
users_map->ids[0] = TORTURE_SSH_USER_ID_ALICE;
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, NULL);
assert_int_equal(rc, 0);
assert_non_null(users_map->names[0]);
assert_string_equal(users_map->names[0], TORTURE_SSH_USER_ALICE);
sftp_name_id_map_free(users_map);
}
static void torture_sftp_users_groups_by_id_groups_only(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_name_id_map groups_map = NULL;
int rc;
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
groups_map = sftp_name_id_map_new(1);
assert_non_null(groups_map);
groups_map->ids[0] = TORTURE_SSH_GROUP_ID_ROOT;
rc = sftp_get_users_groups_by_id(tsftp->sftp, NULL, groups_map);
assert_int_equal(rc, 0);
assert_non_null(groups_map->names[0]);
assert_string_equal(groups_map->names[0], TORTURE_SSH_GROUP_ROOT);
sftp_name_id_map_free(groups_map);
}
static void torture_sftp_users_groups_by_id_multiple(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
sftp_name_id_map users_map = NULL;
sftp_name_id_map groups_map = NULL;
uint32_t i = 0;
int rc;
struct {
uid_t id;
const char *name;
} expected_users[] = {
{TORTURE_SSH_USER_ID_BOB, TORTURE_SSH_USER_BOB},
{TEST_INVALID_ID_1, ""},
{TORTURE_SSH_USER_ID_ALICE, TORTURE_SSH_USER_ALICE},
{TEST_INVALID_ID_2, ""},
{TORTURE_SSH_USER_ID_CHARLIE, TORTURE_SSH_USER_CHARLIE}};
size_t num_expected_users = ARRAY_SIZE(expected_users);
struct {
gid_t id;
const char *name;
} expected_groups[] = {
{TORTURE_SSH_GROUP_ID_ROOT, TORTURE_SSH_GROUP_ROOT},
{TEST_INVALID_ID_1, ""},
{TORTURE_SSH_GROUP_ID_COMMON, TORTURE_SSH_GROUP_COMMON},
{TEST_INVALID_ID_2, ""},
{TORTURE_SSH_GROUP_ID_SSHD, TORTURE_SSH_GROUP_SSHD}};
size_t num_expected_groups = ARRAY_SIZE(expected_groups);
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
users_map = sftp_name_id_map_new(num_expected_users);
assert_non_null(users_map);
groups_map = sftp_name_id_map_new(num_expected_groups);
assert_non_null(groups_map);
for (i = 0; i < num_expected_users; i++) {
users_map->ids[i] = expected_users[i].id;
}
for (i = 0; i < num_expected_groups; i++) {
groups_map->ids[i] = expected_groups[i].id;
}
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
assert_int_equal(rc, 0);
for (i = 0; i < num_expected_users; i++) {
assert_non_null(users_map->names[i]);
assert_string_equal(users_map->names[i], expected_users[i].name);
}
for (i = 0; i < num_expected_groups; i++) {
assert_non_null(groups_map->names[i]);
assert_string_equal(groups_map->names[i], expected_groups[i].name);
}
sftp_name_id_map_free(users_map);
sftp_name_id_map_free(groups_map);
}
static void torture_sftp_users_groups_by_id_negative(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
struct torture_sftp *tsftp = NULL;
int rc;
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
tsftp = s->ssh.tsftp;
assert_non_null(tsftp);
rc = sftp_extension_supported(tsftp->sftp,
"users-groups-by-id@openssh.com",
"1");
assert_int_equal(rc, 1);
rc = sftp_get_users_groups_by_id(NULL, NULL, NULL);
assert_int_equal(rc, SSH_ERROR);
rc = sftp_get_users_groups_by_id(tsftp->sftp, NULL, NULL);
assert_int_equal(rc, SSH_ERROR);
}
int torture_run_tests(void) {
int rc;
struct CMUnitTest tests[] = {
@@ -1330,15 +1602,38 @@ 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_opendir_handles_exhaustion,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(torture_server_sftp_payload_overrun,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(torture_sftp_users_groups_by_id_basic,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(torture_sftp_users_groups_by_id_unknown,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_sftp_users_groups_by_id_users_only,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_sftp_users_groups_by_id_groups_only,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_sftp_users_groups_by_id_multiple,
session_setup_sftp,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_sftp_users_groups_by_id_negative,
session_setup_sftp,
session_teardown),
};
ssh_init();

View File

@@ -53,6 +53,18 @@
#define TORTURE_SSH_USER_CHARLIE "charlie"
#define TORTURE_SSH_USER_NONEUSER "noneuser"
#define TORTURE_SSH_GROUP_ROOT "root"
#define TORTURE_SSH_GROUP_COMMON "users"
#define TORTURE_SSH_GROUP_SSHD "sshd"
#define TORTURE_SSH_USER_ID_BOB 5000
#define TORTURE_SSH_USER_ID_ALICE 5001
#define TORTURE_SSH_USER_ID_CHARLIE 5002
#define TORTURE_SSH_GROUP_ID_ROOT 0
#define TORTURE_SSH_GROUP_ID_COMMON 9000
#define TORTURE_SSH_GROUP_ID_SSHD 65531
/* Used by main to communicate with parse_opt. */
struct argument_s {
const char *pattern;

View File

@@ -39,10 +39,29 @@ static void torture_channel_select(void **state)
close(fd);
}
static void torture_channel_null_session(void **state)
{
ssh_channel channel = NULL;
(void)state;
channel = calloc(1, sizeof(struct ssh_channel_struct));
assert_non_null(channel);
channel->state = SSH_CHANNEL_STATE_OPEN;
channel->session = NULL;
assert_int_equal(ssh_channel_is_open(channel), 0);
free(channel);
}
int torture_run_tests(void) {
int rc;
struct CMUnitTest tests[] = {
cmocka_unit_test(torture_channel_select),
cmocka_unit_test(torture_channel_null_session),
};
ssh_init();

View File

@@ -107,6 +107,76 @@ static void torture_pki_signature(void **state)
ssh_signature_free(sig);
}
static void torture_pki_type_and_hash_from_signature_name(void **state)
{
enum ssh_keytypes_e type;
enum ssh_digest_e hash_type;
int rc;
(void)state; /* unused */
/* Test NULL input */
rc = ssh_key_type_and_hash_from_signature_name(NULL, &type, &hash_type);
assert_int_equal(rc, SSH_ERROR);
/* Test NULL output pointers */
rc = ssh_key_type_and_hash_from_signature_name("ssh-rsa", NULL, &hash_type);
assert_int_equal(rc, SSH_ERROR);
rc = ssh_key_type_and_hash_from_signature_name("ssh-rsa", &type, NULL);
assert_int_equal(rc, SSH_ERROR);
/* Test unknown name */
rc = ssh_key_type_and_hash_from_signature_name("unknown-algo",
&type,
&hash_type);
assert_int_equal(rc, SSH_ERROR);
/* Test valid RSA signatures */
rc =
ssh_key_type_and_hash_from_signature_name("ssh-rsa", &type, &hash_type);
assert_int_equal(rc, SSH_OK);
assert_int_equal(type, SSH_KEYTYPE_RSA);
assert_int_equal(hash_type, SSH_DIGEST_SHA1);
rc = ssh_key_type_and_hash_from_signature_name("rsa-sha2-256",
&type,
&hash_type);
assert_int_equal(rc, SSH_OK);
assert_int_equal(type, SSH_KEYTYPE_RSA);
assert_int_equal(hash_type, SSH_DIGEST_SHA256);
rc = ssh_key_type_and_hash_from_signature_name("rsa-sha2-512",
&type,
&hash_type);
assert_int_equal(rc, SSH_OK);
assert_int_equal(type, SSH_KEYTYPE_RSA);
assert_int_equal(hash_type, SSH_DIGEST_SHA512);
/* Test valid ECDSA signatures */
rc = ssh_key_type_and_hash_from_signature_name("ecdsa-sha2-nistp256",
&type,
&hash_type);
assert_int_equal(rc, SSH_OK);
assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256);
assert_int_equal(hash_type, SSH_DIGEST_SHA256);
/* Test valid ED25519 signature */
rc = ssh_key_type_and_hash_from_signature_name("ssh-ed25519",
&type,
&hash_type);
assert_int_equal(rc, SSH_OK);
assert_int_equal(type, SSH_KEYTYPE_ED25519);
assert_int_equal(hash_type, SSH_DIGEST_AUTO);
/* Test that loose key names are rejected */
rc = ssh_key_type_and_hash_from_signature_name("ecdsa", &type, &hash_type);
assert_int_equal(rc, SSH_ERROR);
rc = ssh_key_type_and_hash_from_signature_name("rsa", &type, &hash_type);
assert_int_equal(rc, SSH_ERROR);
}
struct key_attrs {
int sign;
int verify;
@@ -415,6 +485,7 @@ int torture_run_tests(void) {
struct CMUnitTest tests[] = {
cmocka_unit_test(torture_pki_keytype),
cmocka_unit_test(torture_pki_signature),
cmocka_unit_test(torture_pki_type_and_hash_from_signature_name),
cmocka_unit_test_setup_teardown(torture_pki_verify_mismatch,
setup_cert_dir,
teardown_cert_dir),