Compare commits

..

33 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
Shreyas Mahajan
e927820082 Make headers self-contained and include-order independent
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:07:16 +01:00
ShreyasMahajann
67950c620d misc: Reformat struct ssh_list members to use 4-space indentation
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:07:16 +01:00
Jakub Jelen
31ea4d1213 tests: Negative tests for ssh_pki_ctx_options_set
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
29c503ed7c tests: Remove needless reset to NULL
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
b1a28f7987 tests: Use the new ssh_pki_generate_key() where possible
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
616d165f14 pki_context: Document 0 is valid for bit size (default)
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
b9ecb9283e pki_context: Allow using minimal RSA key size in new API
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
c38edb59f2 examples: Avoid using deprecated ssh_pki_generate
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
def7a679f8 examples: Use separate variable for exit code
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
6f671919ad examples: Use separate variable for fd
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 19:04:57 +01:00
Jakub Jelen
45b1d85fb0 fuzz: Add debugging hints to readme
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 18:44:55 +01:00
Jakub Jelen
e7f4cc9580 knownhosts: Avoid possible memory leak on failed malloc
Thanks oss-fuzz

https://issues.oss-fuzz.com/issues/489362256

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 18:44:55 +01:00
Jakub Jelen
5479b276b2 Use ARRAY_SIZE systematically
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 18:25:22 +01:00
Jakub Jelen
5d7fbcf22a Fix line endings in sftp_attr_fuzzer
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 18:25:22 +01:00
Jakub Jelen
123c442a56 tests: Reformat torture_buffer
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2026-03-16 18:25:22 +01:00
Arthur Chan
4dfcdd96b8 OSS-Fuzz: Add fuzzer for scp functions
Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-13 20:48:53 +01:00
Emmanuel Ugwu
9d36b9dd81 docs: add doxygen documentation and fix inconsistencies
- src/misc.c: added doxygen docs for ssh_get_local_username()
- src/auth.c: added doxygen docs for ssh_kbdint_new(), ssh_kbdint_free(), ssh_kbdint_clean()
- src/bind_config.c: fix @params -> @param, @returns -> @return
- src/bind.c, src/socket.c, src/threads.c: fix @returns -> @return
- include/libssh/callbacks.h: fix @returns -> @return

Signed-off-by: Emmanuel Ugwu <emmanuelugwu121@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-13 20:48:07 +01:00
Rui Li
afa21334b4 tests: Add tests for originalhost/host separation and Match support
Signed-off-by: Rui Li <ruili3422@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-13 20:46:35 +01:00
Rui Li
a2ebc7ea9b Implement originalhost/host separation and Match support
Signed-off-by: Rui Li <ruili3422@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-13 20:46:35 +01:00
Rui Li
1ab8a35c5d Add strict validation mode to ssh_config_parse_uri in config_parser
Signed-off-by: Rui Li <ruili3422@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-13 20:46:35 +01:00
Madhav Vasisth
8782fcec18 agent: Add support for SSH2_AGENTC_REMOVE_IDENTITY
Implement support for the SSH2_AGENTC_REMOVE_IDENTITY
agent protocol message.

The implementation mirrors ssh_agent_sign_data()
and reuses agent_talk(). A single cleanup path is
used to ensure proper resource handling.

Signed-off-by: Madhav Vasisth <mv2363@srmist.edu.in>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-09 18:27:10 +01:00
Pavol Žáčik
8d563f90f3 Add more krb5-related Valgrind suppressions
All newly reported leaks are categorized as
reachable and they mostly relate to global
variables in krb5 which are free'd before
each re-initialization.

Fixes #352.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-03-09 18:20:06 +01:00
95 changed files with 3489 additions and 573 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

@@ -24,7 +24,7 @@ int main(void)
int rv;
/* Generate a new ED25519 private key file */
rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key);
rv = ssh_pki_generate_key(SSH_KEYTYPE_ED25519, NULL, &key);
if (rv != SSH_OK) {
fprintf(stderr, "Failed to generate private key");
return -1;

View File

@@ -35,7 +35,7 @@
struct arguments_st {
enum ssh_keytypes_e type;
unsigned long bits;
int bits;
char *file;
char *passphrase;
char *format;
@@ -321,8 +321,9 @@ list_fingerprint(char *file)
int main(int argc, char *argv[])
{
ssh_pki_ctx ctx = NULL;
ssh_key key = NULL;
int rc = 0;
int ret = EXIT_FAILURE, rc, fd;
char overwrite[1024] = "";
char *pubkey_file = NULL;
@@ -361,15 +362,15 @@ int main(int argc, char *argv[])
}
errno = 0;
rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
if (rc < 0) {
fd = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd < 0) {
if (errno == EEXIST) {
printf("File \"%s\" exists. Overwrite it? (y|n) ", arguments.file);
rc = scanf("%1023s", overwrite);
if (rc > 0 && tolower(overwrite[0]) == 'y') {
rc = open(arguments.file, O_WRONLY);
if (rc > 0) {
close(rc);
fd = open(arguments.file, O_WRONLY);
if (fd > 0) {
close(fd);
errno = 0;
rc = chmod(arguments.file, S_IRUSR | S_IWUSR);
if (rc != 0) {
@@ -391,13 +392,30 @@ int main(int argc, char *argv[])
goto end;
}
} else {
close(rc);
close(fd);
}
/* Create a new PKI Context if needed -- for other types using NULL is ok */
if (arguments.type == SSH_KEYTYPE_RSA && arguments.bits != 0) {
ctx = ssh_pki_ctx_new();
if (ctx == NULL) {
fprintf(stderr, "Error: Failed to allocate PKI context\n");
goto end;
}
rc = ssh_pki_ctx_options_set(ctx,
SSH_PKI_OPTION_RSA_KEY_SIZE,
&arguments.bits);
if (rc != SSH_OK) {
fprintf(stderr, "Error: Failed to set RSA bit size\n");
goto end;
}
}
/* Generate a new private key */
rc = ssh_pki_generate(arguments.type, arguments.bits, &key);
rc = ssh_pki_generate_key(arguments.type, ctx, &key);
if (rc != SSH_OK) {
fprintf(stderr, "Error: Failed to generate keys");
fprintf(stderr, "Error: Failed to generate keys\n");
goto end;
}
@@ -451,24 +469,23 @@ int main(int argc, char *argv[])
pubkey_file = (char *)malloc(strlen(arguments.file) + 5);
if (pubkey_file == NULL) {
rc = ENOMEM;
goto end;
}
sprintf(pubkey_file, "%s.pub", arguments.file);
errno = 0;
rc = open(pubkey_file,
fd = open(pubkey_file,
O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (rc < 0) {
if (fd < 0) {
if (errno == EEXIST) {
printf("File \"%s\" exists. Overwrite it? (y|n) ", pubkey_file);
rc = scanf("%1023s", overwrite);
if (rc > 0 && tolower(overwrite[0]) == 'y') {
rc = open(pubkey_file, O_WRONLY);
if (rc > 0) {
close(rc);
fd = open(pubkey_file, O_WRONLY);
if (fd > 0) {
close(fd);
errno = 0;
rc = chmod(pubkey_file,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
@@ -491,7 +508,7 @@ int main(int argc, char *argv[])
goto end;
}
} else {
close(rc);
close(fd);
}
/* Write the public key */
@@ -501,14 +518,12 @@ int main(int argc, char *argv[])
goto end;
}
end:
if (key != NULL) {
ssh_key_free(key);
}
ret = EXIT_SUCCESS;
if (arguments.file != NULL) {
free(arguments.file);
}
end:
ssh_pki_ctx_free(ctx);
ssh_key_free(key);
free(arguments.file);
if (arguments.passphrase != NULL) {
#ifdef HAVE_EXPLICIT_BZERO
@@ -519,8 +534,6 @@ end:
free(arguments.passphrase);
}
if (pubkey_file != NULL) {
free(pubkey_file);
}
return rc;
free(pubkey_file);
return ret;
}

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

@@ -176,6 +176,17 @@ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
ssh_string ssh_agent_sign_data(ssh_session session,
const ssh_key pubkey,
struct ssh_buffer_struct *data);
/**
* @brief Remove an identity from the SSH agent.
*
* @param session The SSH session.
* @param key The public key to remove.
*
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
*/
int ssh_agent_remove_identity(ssh_session session,
const ssh_key key);
#ifdef __cplusplus

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

@@ -20,6 +20,10 @@
#ifndef _BYTEARRAY_H
#define _BYTEARRAY_H
#include "config.h"
#include <stdint.h>
#define _DATA_BYTE_CONST(data, pos) \
((uint8_t)(((const uint8_t *)(data))[(pos)]))

View File

@@ -522,7 +522,7 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks;
* verifies that the callback pointer exists
* @param p callback pointer
* @param c callback name
* @returns nonzero if callback can be called
* @return nonzero if callback can be called
*/
#define ssh_callbacks_exists(p,c) (\
(p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \
@@ -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

@@ -54,6 +54,11 @@ int ssh_config_get_yesno(char **str, int notfound);
* be stored or NULL if we do not care about the result.
* @param[in] ignore_port Set to true if we should not attempt to parse
* port number.
* @param[in] strict Set to true to validate hostname against RFC1035
* (for resolving to a real host).
* Set to false to only reject shell metacharacters
* (allowing config aliases with non-RFC1035 chars
* like underscores, resolved later via Hostname).
*
* @returns SSH_OK if the provided string is in format of SSH URI,
* SSH_ERROR on failure
@@ -62,7 +67,8 @@ int ssh_config_parse_uri(const char *tok,
char **username,
char **hostname,
char **port,
bool ignore_port);
bool ignore_port,
bool strict);
/**
* @brief: Parse the ProxyJump configuration line and if parsing,

View File

@@ -25,14 +25,19 @@
#ifndef _CRYPTO_H_
#define _CRYPTO_H_
#include <stdbool.h>
#include "config.h"
#include <stdbool.h>
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
#elif defined(HAVE_LIBMBEDCRYPTO)
#include <mbedtls/gcm.h>
#endif
#ifdef HAVE_OPENSSL_ECDH_H
#include <openssl/ecdh.h>
#endif
#include "libssh/wrapper.h"
#ifdef cbc_encrypt
@@ -42,9 +47,6 @@
#undef cbc_decrypt
#endif
#ifdef HAVE_OPENSSL_ECDH_H
#include <openssl/ecdh.h>
#endif
#include "libssh/curve25519.h"
#include "libssh/dh.h"
#include "libssh/ecdh.h"

View File

@@ -23,6 +23,10 @@
#ifndef SRC_DH_GEX_H_
#define SRC_DH_GEX_H_
#include "config.h"
#include "libssh/libssh.h"
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -22,6 +22,10 @@
#ifndef SSH_KNOWNHOSTS_H_
#define SSH_KNOWNHOSTS_H_
#include "config.h"
#include "libssh/libssh.h"
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -24,8 +24,9 @@
#include "config.h"
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
#include "libssh/libssh.h"
typedef gcry_md_hd_t SHACTX;
typedef gcry_md_hd_t SHA256CTX;
typedef gcry_md_hd_t SHA384CTX;

View File

@@ -27,7 +27,6 @@
#include "config.h"
#ifdef HAVE_LIBMBEDCRYPTO
#include <mbedtls/md.h>
#include <mbedtls/bignum.h>
#include <mbedtls/pk.h>
@@ -36,6 +35,8 @@
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/platform.h>
#include "libssh/libssh.h"
typedef mbedtls_md_context_t *SHACTX;
typedef mbedtls_md_context_t *SHA256CTX;
typedef mbedtls_md_context_t *SHA384CTX;

View File

@@ -23,6 +23,11 @@
#include "config.h"
#include <stdint.h>
#include "libssh/callbacks.h"
#include "libssh/libssh.h"
struct ssh_auth_request {
char *username;
int method;

View File

@@ -21,8 +21,9 @@
#ifndef MISC_H_
#define MISC_H_
#ifdef _WIN32
#include "config.h"
#ifdef _WIN32
# ifdef _MSC_VER
# ifndef _SSIZE_T_DEFINED
# undef ssize_t
@@ -31,13 +32,14 @@
# define _SSIZE_T_DEFINED
# endif /* _SSIZE_T_DEFINED */
# endif /* _MSC_VER */
#else
#include <sys/types.h>
#include <stdbool.h>
#endif /* _WIN32 */
#include <stdio.h>
#include "libssh/libssh.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -59,8 +61,8 @@ int ssh_is_ipaddr(const char *str);
/* list processing */
struct ssh_list {
struct ssh_iterator *root;
struct ssh_iterator *end;
struct ssh_iterator *root;
struct ssh_iterator *end;
};
struct ssh_iterator {
@@ -68,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

@@ -21,6 +21,13 @@
#ifndef _OPTIONS_H
#define _OPTIONS_H
#include "config.h"
#include <stdbool.h>
#include <stdio.h>
#include "libssh/libssh.h"
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -21,6 +21,9 @@
#ifndef PACKET_H_
#define PACKET_H_
#include "config.h"
#include "libssh/callbacks.h"
#include "libssh/wrapper.h"
struct ssh_socket_struct;

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

@@ -24,8 +24,12 @@
#include "config.h"
#ifdef HAVE_POLL
#include <poll.h>
#endif
#include "libssh/libssh.h"
#ifdef HAVE_POLL
typedef struct pollfd ssh_pollfd_t;
#else /* HAVE_POLL */

View File

@@ -21,9 +21,13 @@
#ifndef _SCP_H
#define _SCP_H
#include "config.h"
#include <stddef.h>
#include <stdint.h>
#include "libssh/libssh.h"
enum ssh_scp_states {
SSH_SCP_NEW, //Data structure just created
SSH_SCP_WRITE_INITED, //Gave our intention to write

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

@@ -286,6 +286,9 @@ struct ssh_session_struct {
int control_master;
char *control_path;
int address_family;
char *originalhost; /* user-supplied host for config matching */
bool config_hostname_only; /* config hostname path: update host only,
not originalhost */
} opts;
/* server options */

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

@@ -21,7 +21,12 @@
#ifndef SFTP_PRIV_H
#define SFTP_PRIV_H
#include "config.h"
#include <stdbool.h>
#include <stdint.h>
#include "libssh/sftp.h"
#ifdef __cplusplus
extern "C" {

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

@@ -21,13 +21,14 @@
#ifndef WRAPPER_H_
#define WRAPPER_H_
#include "config.h"
#include <stdbool.h>
#include "config.h"
#include "libssh/libssh.h"
#include "libssh/libcrypto.h"
#include "libssh/libgcrypt.h"
#include "libssh/libmbedcrypto.h"
#include "libssh/libssh.h"
#ifdef __cplusplus
extern "C" {

View File

@@ -635,3 +635,91 @@ ssh_string ssh_agent_sign_data(ssh_session session,
return sig_blob;
}
int ssh_agent_remove_identity(ssh_session session,
const ssh_key key)
{
ssh_buffer request = NULL;
ssh_buffer reply = NULL;
ssh_string key_blob = NULL;
uint8_t type = 0;
int rc = SSH_ERROR;
if (session == NULL || key == NULL) {
return SSH_ERROR;
}
if (session->agent == NULL) {
ssh_set_error(session,
SSH_FATAL,
"No agent connection available");
return SSH_ERROR;
}
/* Connect to the agent if not already connected */
if (!ssh_socket_is_open(session->agent->sock)) {
if (agent_connect(session) < 0) {
ssh_set_error(session,
SSH_FATAL,
"Could not connect to SSH agent");
return SSH_ERROR;
}
}
request = ssh_buffer_new();
if (request == NULL) {
ssh_set_error_oom(session);
goto fail;
}
if (ssh_buffer_add_u8(request, SSH2_AGENTC_REMOVE_IDENTITY) < 0) {
ssh_set_error_oom(session);
goto fail;
}
if (ssh_pki_export_pubkey_blob(key, &key_blob) < 0) {
ssh_set_error(session, SSH_FATAL, "Failed to export public key blob");
goto fail;
}
if (ssh_buffer_add_ssh_string(request, key_blob) < 0) {
ssh_set_error_oom(session);
goto fail;
}
reply = ssh_buffer_new();
if (reply == NULL) {
ssh_set_error_oom(session);
goto fail;
}
if (agent_talk(session, request, reply) < 0) {
goto fail;
}
if (ssh_buffer_get_u8(reply, &type) != sizeof(uint8_t)) {
ssh_set_error(session,
SSH_FATAL,
"Failed to read agent reply type");
goto fail;
}
if (agent_failed(type)) {
SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure removing identity");
goto fail;
} else if (type != SSH_AGENT_SUCCESS) {
ssh_set_error(session,
SSH_FATAL,
"Agent refused to remove identity: reply type %u",
type);
goto fail;
}
rc = SSH_OK;
fail:
SSH_STRING_FREE(key_blob);
SSH_BUFFER_FREE(request);
SSH_BUFFER_FREE(reply);
return rc;
}

View File

@@ -1835,6 +1835,14 @@ int ssh_userauth_agent_pubkey(ssh_session session,
return rc;
}
/**
* @internal
*
* @brief Allocates memory for keyboard interactive auth structure.
*
* @return A newly allocated ssh_kbdint structure `kbd` on success, NULL on failure.
* The caller is responsible for freeing allocated memory.
*/
ssh_kbdint ssh_kbdint_new(void)
{
ssh_kbdint kbd;
@@ -1847,7 +1855,11 @@ ssh_kbdint ssh_kbdint_new(void)
return kbd;
}
/**
* @brief Deallocate memory for keyboard interactive auth structure.
*
* @param[in] kbd The keyboard interactive structure to free.
*/
void ssh_kbdint_free(ssh_kbdint kbd)
{
size_t i, n;
@@ -1885,6 +1897,14 @@ void ssh_kbdint_free(ssh_kbdint kbd)
SAFE_FREE(kbd);
}
/**
* @brief Clean a keyboard interactive auth structure.
*
* Clears structure's fields and resets nanswers and nprompts to 0, allowing
* reuse.
*
* @param[in] kbd The keyboard interactive struct to clean
*/
void ssh_kbdint_clean(ssh_kbdint kbd)
{
size_t i, n;

View File

@@ -327,7 +327,7 @@ static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int reve
/** @internal
* @brief returns the current poll handle, or creates it
* @param sshbind the ssh_bind object
* @returns a ssh_poll handle suitable for operation
* @return a ssh_poll handle suitable for operation
*/
ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind)
{

View File

@@ -680,8 +680,8 @@ int ssh_bind_config_parse_file(ssh_bind bind, 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
* @param[in] bind The ssh bind session
* @param[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
@@ -690,7 +690,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
* 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,
* @return SSH_OK on successful parsing the configuration string,
* SSH_ERROR on error
*/
int ssh_bind_config_parse_string(ssh_bind bind, const char *input)

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

@@ -582,9 +582,8 @@ int ssh_connect(ssh_session session)
session->client = 1;
if (session->opts.fd == SSH_INVALID_SOCKET &&
session->opts.host == NULL &&
session->opts.ProxyCommand == NULL)
{
session->opts.originalhost == NULL &&
session->opts.ProxyCommand == NULL) {
ssh_set_error(session, SSH_FATAL, "Hostname required");
return SSH_ERROR;
}

View File

@@ -544,6 +544,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
&jump_host->username,
&jump_host->hostname,
&port,
false,
false);
if (rv != SSH_OK) {
ssh_set_error_invalid(session);
@@ -566,7 +567,12 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
}
} else if (parse_entry) {
/* We actually care only about the first item */
rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false);
rv = ssh_config_parse_uri(cp,
&username,
&hostname,
&port,
false,
false);
if (rv != SSH_OK) {
ssh_set_error_invalid(session);
goto out;
@@ -582,7 +588,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
}
} else {
/* The rest is just sanity-checked to avoid failures later */
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
if (rv != SSH_OK) {
ssh_set_error_invalid(session);
goto out;
@@ -1040,20 +1046,20 @@ static int ssh_config_parse_line_internal(ssh_session session,
break;
case MATCH_ORIGINALHOST:
/* Skip one argument */
/* Here we match only one argument */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL || p[0] == '\0') {
SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
"'%s' requires argument", count, p2);
ssh_set_error(session,
SSH_FATAL,
"line %d: ERROR - Match originalhost keyword "
"requires argument",
count);
SAFE_FREE(x);
return -1;
}
result &=
ssh_config_match(session->opts.originalhost, p, negate);
args++;
SSH_LOG(SSH_LOG_TRACE,
"line %d: Unsupported Match keyword '%s', ignoring",
count,
p2);
result = 0;
break;
case MATCH_HOST:
@@ -1066,7 +1072,11 @@ static int ssh_config_parse_line_internal(ssh_session session,
SAFE_FREE(x);
return -1;
}
result &= ssh_config_match(session->opts.host, p, negate);
result &= ssh_config_match(session->opts.host
? session->opts.host
: session->opts.originalhost,
p,
negate);
args++;
break;
@@ -1154,7 +1164,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
int ok = 0, result = -1;
*parsing = 0;
lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
lowerhost = (session->opts.originalhost)
? ssh_lowercase(session->opts.originalhost)
: NULL;
for (p = ssh_config_get_str_tok(&s, NULL);
p != NULL && p[0] != '\0';
p = ssh_config_get_str_tok(&s, NULL)) {
@@ -1181,7 +1193,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
if (z == NULL) {
z = strdup(p);
}
session->opts.config_hostname_only = true;
ssh_options_set(session, SSH_OPTIONS_HOST, z);
session->opts.config_hostname_only = false;
free(z);
}
break;
@@ -1670,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
*/
@@ -1696,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
*/
@@ -1734,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

@@ -172,7 +172,8 @@ int ssh_config_parse_uri(const char *tok,
char **username,
char **hostname,
char **port,
bool ignore_port)
bool ignore_port,
bool strict)
{
char *endp = NULL;
long port_n;
@@ -243,13 +244,31 @@ int ssh_config_parse_uri(const char *tok,
if (*hostname == NULL) {
goto error;
}
/* if not an ip, check syntax */
rc = ssh_is_ipaddr(*hostname);
if (rc == 0) {
rc = ssh_check_hostname_syntax(*hostname);
if (rc != SSH_OK) {
if (strict) {
/* if not an ip, check syntax */
rc = ssh_is_ipaddr(*hostname);
if (rc == 0) {
rc = ssh_check_hostname_syntax(*hostname);
if (rc != SSH_OK) {
goto error;
}
}
} else {
/* Reject shell metacharacters to allow config aliases with
* non-RFC1035 chars (e.g. %, _). Modeled on OpenSSH's
* valid_hostname() in ssh.c. */
const char *c = NULL;
if ((*hostname)[0] == '-') {
goto error;
}
for (c = *hostname; *c != '\0'; c++) {
char *is_meta = strchr("'`\"$\\;&<>|(){},", *c);
int is_space = isspace((unsigned char)*c);
int is_ctrl = iscntrl((unsigned char)*c);
if (is_meta != NULL || is_space || is_ctrl) {
goto error;
}
}
}
}
/* Skip also the closing bracket */

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

@@ -310,7 +310,11 @@ static int ssh_known_hosts_read_entries(const char *match,
}
}
if (entry != NULL) {
ssh_list_append(*entries, entry);
rc = ssh_list_append(*entries, entry);
if (rc != SSH_OK) {
ssh_knownhosts_entry_free(entry);
goto error;
}
}
}

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,6 +214,12 @@ struct tm *ssh_localtime(const time_t *timer, struct tm *result)
return result;
}
/** @internal
* @brief Get the username of the currently running process.
*
* @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)
{
DWORD size = 0;
@@ -234,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;
@@ -257,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;
@@ -322,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) {
@@ -357,6 +389,12 @@ int ssh_dir_writeable(const char *path)
return 0;
}
/** @internal
* @brief Get the username of the currently running process.
*
* @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)
{
struct passwd pwd;
@@ -381,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;
@@ -394,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;
@@ -428,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;
@@ -451,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;
@@ -471,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;
@@ -776,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));
@@ -786,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;
@@ -800,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)
@@ -807,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;
@@ -882,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;
@@ -907,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;
@@ -1245,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};
@@ -1347,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
@@ -1432,6 +1560,8 @@ char *ssh_path_expand_escape(ssh_session session, const char *s)
case 'h':
if (session->opts.host) {
x = strdup(session->opts.host);
} else if (session->opts.originalhost) {
x = strdup(session->opts.originalhost);
} else {
ssh_set_error(session, SSH_FATAL, "Cannot expand host");
free(buf);
@@ -1777,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;
@@ -1797,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

@@ -23,6 +23,7 @@
*/
#include "config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -106,6 +107,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
}
}
if (src->opts.originalhost != NULL) {
new->opts.originalhost = strdup(src->opts.originalhost);
if (new->opts.originalhost == NULL) {
ssh_free(new);
return -1;
}
}
if (src->opts.bindaddr != NULL) {
new->opts.bindaddr = strdup(src->opts.bindaddr);
if (new->opts.bindaddr == NULL) {
@@ -279,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,
@@ -718,18 +741,52 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
return -1;
} else {
char *username = NULL, *hostname = NULL;
rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true);
if (rc != SSH_OK) {
char *strict_hostname = NULL;
/* Non-strict parse: reject shell metacharacters */
rc = ssh_config_parse_uri(value,
&username,
&hostname,
NULL,
true,
false);
if (rc != SSH_OK || hostname == NULL) {
SAFE_FREE(username);
SAFE_FREE(hostname);
ssh_set_error_invalid(session);
return -1;
}
/* Non-strict passed: set username and originalhost */
if (username != NULL) {
SAFE_FREE(session->opts.username);
session->opts.username = username;
}
if (hostname != NULL) {
if (!session->opts.config_hostname_only) {
SAFE_FREE(session->opts.originalhost);
session->opts.originalhost = hostname;
} else {
SAFE_FREE(hostname);
}
/* Strict parse: set host only if valid hostname or IP */
rc = ssh_config_parse_uri(value,
NULL,
&strict_hostname,
NULL,
true,
true);
if (rc != SSH_OK || strict_hostname == NULL) {
SAFE_FREE(session->opts.host);
session->opts.host = hostname;
SAFE_FREE(strict_hostname);
if (session->opts.config_hostname_only) {
/* Config path: Hostname must be valid */
ssh_set_error_invalid(session);
return -1;
}
} else {
SAFE_FREE(session->opts.host);
session->opts.host = strict_hostname;
}
}
break;
@@ -1646,7 +1703,8 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
switch(type)
{
case SSH_OPTIONS_HOST:
src = session->opts.host;
src = session->opts.host ? session->opts.host
: session->opts.originalhost;
break;
case SSH_OPTIONS_USER:
@@ -1980,7 +2038,7 @@ int ssh_options_parse_config(ssh_session session, const char *filename)
if (session == NULL) {
return -1;
}
if (session->opts.host == NULL) {
if (session->opts.originalhost == NULL) {
ssh_set_error_invalid(session);
return -1;
}
@@ -2037,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

@@ -110,7 +110,7 @@ void ssh_pki_ctx_free(ssh_pki_ctx context)
* Set the RSA key size in bits for key generation.
* Typically 2048, 3072, or 4096 bits. Must be greater
* than or equal to 1024, as anything below is considered
* insecure.
* insecure. Use 0 (default) to use default key size (3072).
*
* - SSH_PKI_OPTION_SK_APPLICATION (const char *):
* The Relying Party identifier (application string) that
@@ -191,7 +191,7 @@ int ssh_pki_ctx_options_set(ssh_pki_ctx context,
if (value == NULL) {
SSH_LOG(SSH_LOG_WARN, "RSA key size pointer must not be NULL");
return SSH_ERROR;
} else if (*(int *)value != 0 && *(int *)value <= RSA_MIN_KEY_SIZE) {
} else if (*(int *)value != 0 && *(int *)value < RSA_MIN_KEY_SIZE) {
SSH_LOG(
SSH_LOG_WARN,
"RSA key size must be greater than %d bits or 0 for default",

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

@@ -406,6 +406,7 @@ void ssh_free(ssh_session session)
SAFE_FREE(session->opts.bindaddr);
SAFE_FREE(session->opts.username);
SAFE_FREE(session->opts.host);
SAFE_FREE(session->opts.originalhost);
SAFE_FREE(session->opts.homedir);
SAFE_FREE(session->opts.sshdir);
SAFE_FREE(session->opts.knownhosts);
@@ -1009,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,
@@ -444,7 +447,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p,
/** @internal
* @brief returns the poll handle corresponding to the socket,
* creates it if it does not exist.
* @returns allocated and initialized ssh_poll_handle object
*
* @return allocated and initialized ssh_poll_handle object
*/
ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s)
{
@@ -1062,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;
@@ -1075,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

@@ -38,7 +38,6 @@ static struct ssh_threads_callbacks_struct *user_callbacks = NULL;
/** @internal
* @brief inits the threading with the backend cryptographic libraries
*/
int ssh_threads_init(void)
{
static int threads_initialized = 0;
@@ -63,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();
@@ -84,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

@@ -10,6 +10,7 @@
#include "libssh/callbacks.h"
#include "libssh/libssh.h"
#include <libssh/agent.h>
#include "libssh/priv.h"
#include <errno.h>
@@ -286,6 +287,96 @@ static void torture_auth_agent_forwarding(void **state)
}
}
static int agent_session_setup(void **state)
{
struct torture_state *s = *state;
int verbosity = torture_libssh_verbosity();
int rc;
s->ssh.ssh.session = ssh_new();
assert_non_null(s->ssh.ssh.session);
rc = ssh_options_set(s->ssh.ssh.session,
SSH_OPTIONS_LOG_VERBOSITY,
&verbosity);
assert_int_equal(rc, SSH_OK);
/* No callbacks needed — only talking to the local agent.
* The group setup already started the agent and loaded keys.
* Do NOT call torture_setup_ssh_agent here — that would spawn
* a second agent and overwrite SSH_AUTH_SOCK. */
s->ssh.ssh.cb_state = NULL;
s->ssh.ssh.callbacks = NULL;
return 0;
}
static void torture_agent_remove_identity(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.ssh.session;
ssh_key key = NULL;
char *comment = NULL;
uint32_t count_before = 0;
uint32_t count_after = 0;
int rc;
assert_non_null(session);
assert_true(ssh_agent_is_running(session));
count_before = ssh_agent_get_ident_count(session);
assert_true(count_before > 0);
key = ssh_agent_get_first_ident(session, &comment);
assert_non_null(key);
assert_non_null(comment);
rc = ssh_agent_remove_identity(session, key);
assert_int_equal(rc, SSH_OK);
count_after = ssh_agent_get_ident_count(session);
assert_int_equal(count_after, count_before - 1);
ssh_key_free(key);
ssh_string_free_char(comment);
}
static void torture_agent_remove_identity_negative(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.ssh.session;
int rc;
assert_non_null(session);
/* NULL key should return SSH_ERROR */
rc = ssh_agent_remove_identity(session, NULL);
assert_int_equal(rc, SSH_ERROR);
}
static void torture_agent_remove_identity_nonexistent(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.ssh.session;
ssh_key key = NULL;
int rc;
assert_non_null(session);
assert_true(ssh_agent_is_running(session));
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key);
assert_int_equal(rc, SSH_OK);
assert_non_null(key);
/* Key not in agent should fail */
rc = ssh_agent_remove_identity(session, key);
assert_int_equal(rc, SSH_ERROR);
ssh_key_free(key);
}
/* Session setup function that configures SSH agent */
static int session_setup(void **state)
{
@@ -300,7 +391,6 @@ static int session_setup(void **state)
/* Create a new session */
s->ssh.ssh.session = ssh_new();
assert_non_null(s->ssh.ssh.session);
rc = ssh_options_set(s->ssh.ssh.session,
SSH_OPTIONS_LOG_VERBOSITY,
&verbosity);
@@ -342,19 +432,32 @@ static int session_setup(void **state)
int torture_run_tests(void)
{
int rc;
struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_agent_remove_identity,
agent_session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_negative,
agent_session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_nonexistent,
agent_session_setup,
session_teardown),
};
ssh_init();
/* Simplify the CMocka test filter handling */
#if defined HAVE_CMOCKA_SET_TEST_FILTER
cmocka_set_message_output(CM_OUTPUT_STDOUT);
#endif
/* Apply test filtering */
torture_filter_tests(tests);
rc = cmocka_run_group_tests(tests,
@@ -365,5 +468,4 @@ int torture_run_tests(void)
return rc;
}
#endif

View File

@@ -167,7 +167,7 @@ static void torture_connect_addrfamily(void **state)
{SSH_ADDRESS_FAMILY_INET6, "afinet6", SSH_OK},
};
int aftest_count = sizeof(aftests) / sizeof(aftests[0]);
int aftest_count = ARRAY_SIZE(aftests);
for (int i = 0; i < aftest_count; ++i) {
struct aftest const *t = &aftests[i];

View File

@@ -94,8 +94,7 @@ static void torture_kex_basic_functionality(void **state)
assert_non_null(kex_algo);
is_valid_algo = false;
valid_algorithms_count =
sizeof(valid_algorithms) / sizeof(valid_algorithms[0]);
valid_algorithms_count = ARRAY_SIZE(valid_algorithms);
for (i = 0; i < valid_algorithms_count; i++) {
if (strcmp(kex_algo, valid_algorithms[i]) == 0) {
is_valid_algo = true;

View File

@@ -1,8 +1,22 @@
project(fuzzing CXX)
# Build SSH server mock helper as object library
add_library(ssh_server_mock_obj OBJECT ssh_server_mock.c)
target_link_libraries(ssh_server_mock_obj PRIVATE ${TORTURE_LINK_LIBRARIES})
macro(fuzzer name)
add_executable(${name} ${name}.c)
target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES})
# Add ssh_server_mock dependency for scp and sftp fuzzers
if (${name} STREQUAL "ssh_scp_fuzzer" OR ${name} STREQUAL "ssh_sftp_fuzzer")
target_sources(${name} PRIVATE $<TARGET_OBJECTS:ssh_server_mock_obj>)
target_link_libraries(${name} PRIVATE pthread)
endif()
if (WITH_COVERAGE)
append_coverage_compiler_flags_to_target(${name})
endif (WITH_COVERAGE)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set_target_properties(${name}
PROPERTIES
@@ -36,6 +50,7 @@ fuzzer(ssh_pubkey_fuzzer)
fuzzer(ssh_sftp_attr_fuzzer)
fuzzer(ssh_sshsig_fuzzer)
if (WITH_SERVER)
fuzzer(ssh_scp_fuzzer)
fuzzer(ssh_server_fuzzer)
fuzzer(ssh_bind_config_fuzzer)
endif (WITH_SERVER)

View File

@@ -129,6 +129,11 @@ pass environment variables to the container:
python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264
In case the nalloc fuzzer fails, running the test with `NALLOC_VERBOSE=1`
environment variable will help to pinpoint the failed malloc:
python infra/helper.py reproduce -eNALLOC_VERBOSE=1 libssh ssh_known_hosts_fuzzer_nalloc ~/Downloads/clusterfuzz-testcase-minimized-ssh_known_hosts_fuzzer_nalloc-5555469543604224
### Fix the issue and verify the fix
Now, we can properly investigate the issue and once we have a fix, we can

206
tests/fuzz/ssh_scp_fuzzer.c Normal file
View File

@@ -0,0 +1,206 @@
/*
* Copyright 2026 libssh authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define LIBSSH_STATIC 1
#include <libssh/libssh.h>
#include <libssh/scp.h>
#include "nallocinc.c"
#include "ssh_server_mock.h"
static void _fuzz_finalize(void)
{
ssh_finalize();
}
int LLVMFuzzerInitialize(int *argc, char ***argv)
{
(void)argc;
nalloc_init(*argv[0]);
ssh_init();
atexit(_fuzz_finalize);
ssh_mock_write_hostkey(SSH_MOCK_HOSTKEY_PATH);
return 0;
}
/* Helper function to test one cipher/HMAC combination */
static int test_scp_with_cipher(const uint8_t *data,
size_t size,
const char *cipher,
const char *hmac)
{
int socket_fds[2] = {-1, -1};
ssh_session client_session = NULL;
ssh_scp scp = NULL, scp_recursive = NULL;
char buf[256] = {0};
pthread_t srv_thread;
/* Configure mock SSH server with fuzzer data */
struct ssh_mock_server_config server_config = {
.protocol_data = data,
.protocol_data_size = size,
.exec_callback = ssh_mock_send_raw_data,
.subsystem_callback = NULL,
.callback_userdata = NULL,
.cipher = cipher,
.hmac = hmac,
.server_socket = -1,
.client_socket = -1,
.server_ready = false,
.server_error = false,
};
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds) != 0) {
goto cleanup;
}
server_config.server_socket = socket_fds[0];
server_config.client_socket = socket_fds[1];
if (ssh_mock_server_start(&server_config, &srv_thread) != 0) {
goto cleanup;
}
client_session = ssh_new();
if (client_session == NULL) {
goto cleanup_thread;
}
/* Configure client with specified cipher/HMAC */
ssh_options_set(client_session, SSH_OPTIONS_FD, &socket_fds[1]);
ssh_options_set(client_session, SSH_OPTIONS_HOST, "localhost");
ssh_options_set(client_session, SSH_OPTIONS_USER, "fuzz");
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_C_S, cipher);
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_S_C, cipher);
ssh_options_set(client_session, SSH_OPTIONS_HMAC_C_S, hmac);
ssh_options_set(client_session, SSH_OPTIONS_HMAC_S_C, hmac);
/* Set timeout for operations (1 second) */
long timeout = 1;
ssh_options_set(client_session, SSH_OPTIONS_TIMEOUT, &timeout);
if (ssh_connect(client_session) != SSH_OK) {
goto cleanup_thread;
}
if (ssh_userauth_none(client_session, NULL) != SSH_AUTH_SUCCESS) {
goto cleanup_thread;
}
scp = ssh_scp_new(client_session, SSH_SCP_READ, "/tmp/fuzz");
if (scp == NULL) {
goto cleanup_thread;
}
if (ssh_scp_init(scp) != SSH_OK) {
goto cleanup_thread;
}
if (size > 0) {
size_t copy_size = size < sizeof(buf) ? size : sizeof(buf);
memcpy(buf, data, copy_size);
}
/* Fuzz all SCP API functions in read mode */
ssh_scp_pull_request(scp);
ssh_scp_request_get_filename(scp);
ssh_scp_request_get_permissions(scp);
ssh_scp_request_get_size64(scp);
ssh_scp_request_get_size(scp);
ssh_scp_request_get_warning(scp);
ssh_scp_accept_request(scp);
ssh_scp_deny_request(scp, "Denied by fuzzer");
ssh_scp_read(scp, buf, sizeof(buf));
/* Final fuzz of scp pull request after all the calls */
ssh_scp_pull_request(scp);
/* Fuzz SCP in write/upload + recursive directory mode. */
scp_recursive = ssh_scp_new(client_session,
SSH_SCP_WRITE | SSH_SCP_RECURSIVE,
"/tmp/fuzz-recursive");
if (scp_recursive != NULL) {
if (ssh_scp_init(scp_recursive) == SSH_OK) {
ssh_scp_push_directory(scp_recursive, "fuzz-dir", 0755);
ssh_scp_push_file(scp_recursive, "fuzz-file", sizeof(buf), 0644);
ssh_scp_write(scp_recursive, buf, sizeof(buf));
ssh_scp_leave_directory(scp_recursive);
}
}
cleanup_thread:
pthread_join(srv_thread, NULL);
cleanup:
if (scp_recursive != NULL) {
ssh_scp_close(scp_recursive);
ssh_scp_free(scp_recursive);
}
if (scp) {
ssh_scp_close(scp);
ssh_scp_free(scp);
}
if (client_session) {
ssh_disconnect(client_session);
ssh_free(client_session);
}
if (socket_fds[0] >= 0)
close(socket_fds[0]);
if (socket_fds[1] >= 0)
close(socket_fds[1]);
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
assert(nalloc_start(data, size) > 0);
/* Test all cipher/HMAC combinations exhaustively */
const char *ciphers[] = {
"none",
"aes128-ctr",
"aes256-ctr",
"aes128-cbc",
};
const char *hmacs[] = {
"none",
"hmac-sha1",
"hmac-sha2-256",
};
int num_ciphers = sizeof(ciphers) / sizeof(ciphers[0]);
int num_hmacs = sizeof(hmacs) / sizeof(hmacs[0]);
for (int i = 0; i < num_ciphers; i++) {
for (int j = 0; j < num_hmacs; j++) {
test_scp_with_cipher(data, size, ciphers[i], hmacs[j]);
}
}
nalloc_end();
return 0;
}

View File

@@ -0,0 +1 @@
C0644 50 ../../../etc/passwd

View File

@@ -0,0 +1 @@
C0644 10 dir/file.txt

View File

@@ -0,0 +1 @@
C 100 test

View File

@@ -0,0 +1 @@
C0644 10 ..

View File

@@ -0,0 +1 @@
C0755 1024 executable.sh

View File

@@ -0,0 +1 @@
C0644 999999999999 huge.dat

View File

@@ -0,0 +1 @@
T1234567890 0 1234567890 0

View File

@@ -0,0 +1 @@
C0644 100 test.txt

View File

@@ -0,0 +1 @@
Warning: Test warning

View File

@@ -0,0 +1 @@
C0644 10 .

View File

@@ -0,0 +1 @@
Error: Test error

View File

@@ -0,0 +1 @@
Xunknown command

View File

@@ -0,0 +1 @@
C0644 test

View File

@@ -0,0 +1 @@
D0755 0 mydir

View File

@@ -0,0 +1 @@
C0644 abc test

View File

@@ -0,0 +1,262 @@
/*
* Copyright 2026 libssh authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ssh_server_mock.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define LIBSSH_STATIC 1
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
/* Fixed ed25519 key for all mock servers */
const char *ssh_mock_ed25519_key_pem =
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
"QyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zAAAAJgbsYq1G7GK\n"
"tQAAAAtzc2gtZWQyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zA\n"
"AAAEAkGaLvQwKMbGVRk2M8cz7gqWvpBKuHkuekJxIBQrUJl2kU7z8l9iUiqDr6+rHW8MpY\n"
"OrEkfHDi0GqdB+I6N/bMAAAAEGZ1enotZWQyNTUxOS1rZXkBAgMEBQ==\n"
"-----END OPENSSH PRIVATE KEY-----\n";
/* Internal server session data */
struct mock_session_data {
ssh_channel channel;
struct ssh_mock_server_config *config;
};
/* Auth callback - always accepts "none" auth */
static int mock_auth_none(ssh_session session, const char *user, void *userdata)
{
(void)session;
(void)user;
(void)userdata;
return SSH_AUTH_SUCCESS;
}
/* Channel open callback */
static ssh_channel mock_channel_open(ssh_session session, void *userdata)
{
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
sdata->channel = ssh_channel_new(session);
return sdata->channel;
}
/* Exec request callback - for SCP */
static int mock_channel_exec(ssh_session session,
ssh_channel channel,
const char *command,
void *userdata)
{
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
(void)session;
(void)command;
if (sdata->config->exec_callback) {
return sdata->config->exec_callback(channel,
sdata->config->protocol_data,
sdata->config->protocol_data_size,
sdata->config->callback_userdata);
}
return SSH_OK;
}
/* Subsystem request callback - for SFTP */
static int mock_channel_subsystem(ssh_session session,
ssh_channel channel,
const char *subsystem,
void *userdata)
{
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
(void)session;
(void)subsystem;
if (sdata->config->subsystem_callback) {
return sdata->config->subsystem_callback(
channel,
sdata->config->protocol_data,
sdata->config->protocol_data_size,
sdata->config->callback_userdata);
}
return SSH_OK;
}
/* Server thread implementation */
static void *server_thread_func(void *arg)
{
struct ssh_mock_server_config *config =
(struct ssh_mock_server_config *)arg;
ssh_bind sshbind = NULL;
ssh_session session = NULL;
ssh_event event = NULL;
struct mock_session_data sdata = {0};
sdata.config = config;
struct ssh_server_callbacks_struct server_cb = {
.userdata = &sdata,
.auth_none_function = mock_auth_none,
.channel_open_request_session_function = mock_channel_open,
};
struct ssh_channel_callbacks_struct channel_cb = {
.userdata = &sdata,
.channel_exec_request_function = mock_channel_exec,
.channel_subsystem_request_function = mock_channel_subsystem,
};
bool no = false;
sshbind = ssh_bind_new();
if (sshbind == NULL) {
config->server_error = true;
return NULL;
}
session = ssh_new();
if (session == NULL) {
ssh_bind_free(sshbind);
config->server_error = true;
return NULL;
}
const char *cipher = config->cipher ? config->cipher : "aes128-ctr";
const char *hmac = config->hmac ? config->hmac : "hmac-sha1";
ssh_bind_options_set(sshbind,
SSH_BIND_OPTIONS_HOSTKEY,
SSH_MOCK_HOSTKEY_PATH);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, cipher);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, cipher);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, hmac);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, hmac);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no);
ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE);
ssh_callbacks_init(&server_cb);
ssh_set_server_callbacks(session, &server_cb);
if (ssh_bind_accept_fd(sshbind, session, config->server_socket) != SSH_OK) {
ssh_free(session);
ssh_bind_free(sshbind);
config->server_error = true;
return NULL;
}
config->server_ready = true;
event = ssh_event_new();
if (event == NULL) {
ssh_disconnect(session);
ssh_free(session);
ssh_bind_free(sshbind);
return NULL;
}
if (ssh_handle_key_exchange(session) == SSH_OK) {
ssh_event_add_session(event, session);
for (int i = 0; i < 50 && !sdata.channel; i++) {
ssh_event_dopoll(event, 1);
}
if (sdata.channel) {
ssh_callbacks_init(&channel_cb);
ssh_set_channel_callbacks(sdata.channel, &channel_cb);
int max_iterations = 30;
for (int iter = 0; iter < max_iterations &&
!ssh_channel_is_closed(sdata.channel) &&
!ssh_channel_is_eof(sdata.channel);
iter++) {
if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
break;
}
}
}
}
if (event)
ssh_event_free(event);
if (session) {
ssh_disconnect(session);
ssh_free(session);
}
if (sshbind)
ssh_bind_free(sshbind);
return NULL;
}
/* Public API - start mock SSH server */
int ssh_mock_server_start(struct ssh_mock_server_config *config,
pthread_t *thread)
{
if (!config || !thread)
return -1;
config->server_ready = false;
config->server_error = false;
if (pthread_create(thread, NULL, server_thread_func, config) != 0) {
return -1;
}
for (int i = 0; i < 50 && !config->server_ready && !config->server_error;
i++) {
usleep(100);
}
return config->server_error ? -1 : 0;
}
/* Generic protocol callback - sends raw fuzzer data for any protocol */
int ssh_mock_send_raw_data(void *channel,
const void *data,
size_t size,
void *userdata)
{
(void)userdata;
ssh_channel target_channel = (ssh_channel)channel;
/* Send raw fuzzer data - let protocol parser interpret it */
if (size > 0) {
ssh_channel_write(target_channel, data, size);
}
/* Close channel to signal completion */
ssh_channel_send_eof(target_channel);
ssh_channel_close(target_channel);
return SSH_OK;
}
/* Write fixed ed25519 host key to file */
int ssh_mock_write_hostkey(const char *path)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
return -1;
size_t len = strlen(ssh_mock_ed25519_key_pem);
size_t nwritten = fwrite(ssh_mock_ed25519_key_pem, 1, len, fp);
fclose(fp);
return (nwritten == len) ? 0 : -1;
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2026 libssh authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SSH_SERVER_MOCK_H
#define SSH_SERVER_MOCK_H
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
/* Server callback type */
typedef int (*ssh_mock_callback_fn)(void *channel,
const void *data,
size_t size,
void *userdata);
/* Mock server configuration */
struct ssh_mock_server_config {
const uint8_t *protocol_data;
size_t protocol_data_size;
ssh_mock_callback_fn exec_callback;
ssh_mock_callback_fn subsystem_callback;
void *callback_userdata;
const char *cipher;
const char *hmac;
int server_socket;
int client_socket;
bool server_ready;
bool server_error;
};
/* Public API functions */
int ssh_mock_server_start(struct ssh_mock_server_config *config,
pthread_t *thread);
int ssh_mock_send_raw_data(void *channel,
const void *data,
size_t size,
void *userdata);
int ssh_mock_write_hostkey(const char *path);
/* Fixed ed25519 key constant */
extern const char *ssh_mock_ed25519_key_pem;
/* Centralized hostkey path used by all mock servers */
#define SSH_MOCK_HOSTKEY_PATH "/tmp/libssh_mock_fuzz_key"
#endif

View File

@@ -1,131 +1,133 @@
/*
* Copyright 2026 libssh authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LIBSSH_STATIC 1
#include "libssh/libssh.h"
#include "libssh/sftp.h"
#include "libssh/sftp_priv.h"
#include "nallocinc.c"
/* SFTP protocol version constants */
#define SFTP_PROTOCOL_VERSION_3 3
#define SFTP_PROTOCOL_VERSION_4 4
/* Flags for sftp_parse_attr expectname parameter */
#define SFTP_EXPECT_NAME 1
#define SFTP_NO_NAME 0
/*
* Helper to create a minimal sftp_session for fuzzing.
* We don't use sftp_new() as it requires a real SSH connection.
*/
static sftp_session create_minimal_sftp_session(ssh_session session)
{
sftp_session sftp;
sftp = calloc(1, sizeof(struct sftp_session_struct));
if (sftp == NULL) {
return NULL;
}
sftp->session = session;
return sftp;
}
static void _fuzz_finalize(void)
{
ssh_finalize();
}
int LLVMFuzzerInitialize(int *argc, char ***argv)
{
(void)argc;
nalloc_init(*argv[0]);
ssh_init();
atexit(_fuzz_finalize);
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
ssh_session session = NULL;
sftp_session sftp = NULL;
ssh_buffer buffer = NULL;
sftp_attributes attr = NULL;
int versions[] = {
SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3,
SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4
};
int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME};
size_t i;
/* Minimum bytes for a valid SFTP message */
if (size == 0) {
return 0;
}
assert(nalloc_start(data, size) > 0);
/* Allocate shared resources once for all test iterations */
session = ssh_new();
if (session == NULL) {
goto cleanup;
}
sftp = create_minimal_sftp_session(session);
if (sftp == NULL) {
goto cleanup;
}
buffer = ssh_buffer_new();
if (buffer == NULL) {
goto cleanup;
}
/* Main fuzzing target: sftp_parse_attr */
/* Parses untrusted SFTP messages from client */
/* Test all combinations (v3/v4, with/without name) */
for (i = 0; i < (sizeof(versions) / sizeof(versions[0])); i++) {
sftp->version = versions[i];
/* Reset and repopulate buffer for each iteration */
ssh_buffer_reinit(buffer);
if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) {
attr = sftp_parse_attr(sftp, buffer, expectnames[i]);
sftp_attributes_free(attr);
attr = NULL;
}
}
cleanup:
ssh_buffer_free(buffer);
free(sftp);
ssh_free(session);
nalloc_end();
return 0;
}
/*
* Copyright 2026 libssh authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LIBSSH_STATIC 1
#include "libssh/libssh.h"
#include "libssh/priv.h"
#include "libssh/sftp.h"
#include "libssh/sftp_priv.h"
#include "nallocinc.c"
/* SFTP protocol version constants */
#define SFTP_PROTOCOL_VERSION_3 3
#define SFTP_PROTOCOL_VERSION_4 4
/* Flags for sftp_parse_attr expectname parameter */
#define SFTP_EXPECT_NAME 1
#define SFTP_NO_NAME 0
/*
* Helper to create a minimal sftp_session for fuzzing.
* We don't use sftp_new() as it requires a real SSH connection.
*/
static sftp_session create_minimal_sftp_session(ssh_session session)
{
sftp_session sftp;
sftp = calloc(1, sizeof(struct sftp_session_struct));
if (sftp == NULL) {
return NULL;
}
sftp->session = session;
return sftp;
}
static void _fuzz_finalize(void)
{
ssh_finalize();
}
int LLVMFuzzerInitialize(int *argc, char ***argv)
{
(void)argc;
nalloc_init(*argv[0]);
ssh_init();
atexit(_fuzz_finalize);
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
ssh_session session = NULL;
sftp_session sftp = NULL;
ssh_buffer buffer = NULL;
sftp_attributes attr = NULL;
int versions[] = {
SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3,
SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4
};
int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME};
size_t i;
/* Minimum bytes for a valid SFTP message */
if (size == 0) {
return 0;
}
assert(nalloc_start(data, size) > 0);
/* Allocate shared resources once for all test iterations */
session = ssh_new();
if (session == NULL) {
goto cleanup;
}
sftp = create_minimal_sftp_session(session);
if (sftp == NULL) {
goto cleanup;
}
buffer = ssh_buffer_new();
if (buffer == NULL) {
goto cleanup;
}
/* Main fuzzing target: sftp_parse_attr */
/* Parses untrusted SFTP messages from client */
/* Test all combinations (v3/v4, with/without name) */
for (i = 0; i < ARRAY_SIZE(versions); i++) {
sftp->version = versions[i];
/* Reset and repopulate buffer for each iteration */
ssh_buffer_reinit(buffer);
if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) {
attr = sftp_parse_attr(sftp, buffer, expectnames[i]);
sftp_attributes_free(attr);
attr = NULL;
}
}
cleanup:
ssh_buffer_free(buffer);
free(sftp);
ssh_free(session);
nalloc_end();
return 0;
}

View File

@@ -849,10 +849,10 @@ static int pkd_run_tests(void) {
};
/* Test list is populated depending on which clients are enabled. */
struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) +
(sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) +
(sizeof(putty_tests) / sizeof(putty_tests[0])) +
(sizeof(noop_tests) / sizeof(noop_tests[0]))];
struct CMUnitTest all_tests[ARRAY_SIZE(openssh_tests) +
ARRAY_SIZE(dropbear_tests) +
ARRAY_SIZE(putty_tests) +
ARRAY_SIZE(noop_tests)];
memset(&all_tests[0], 0x0, sizeof(all_tests));
/* Generate client keys and populate test list for each enabled client. */
@@ -860,10 +860,10 @@ static int pkd_run_tests(void) {
setup_openssh_client_keys();
if (ssh_fips_mode()) {
memcpy(&all_tests[tindex], &openssh_fips_tests[0], sizeof(openssh_fips_tests));
tindex += (sizeof(openssh_fips_tests) / sizeof(openssh_fips_tests[0]));
tindex += ARRAY_SIZE(openssh_fips_tests);
} else {
memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests));
tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0]));
tindex += ARRAY_SIZE(openssh_tests);
}
}
@@ -871,7 +871,7 @@ static int pkd_run_tests(void) {
setup_dropbear_client_keys();
if (!ssh_fips_mode()) {
memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests));
tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0]));
tindex += ARRAY_SIZE(dropbear_tests);
}
}
@@ -879,12 +879,12 @@ static int pkd_run_tests(void) {
setup_putty_client_keys();
if (!ssh_fips_mode()) {
memcpy(&all_tests[tindex], &putty_tests[0], sizeof(putty_tests));
tindex += (sizeof(putty_tests) / sizeof(putty_tests[0]));
tindex += ARRAY_SIZE(putty_tests);
}
}
memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests));
tindex += (sizeof(noop_tests) / sizeof(noop_tests[0]));
tindex += ARRAY_SIZE(noop_tests);
if ((pkd_dargs.opts.testname == NULL) &&
(pkd_dargs.opts.testmatch == NULL)) {
@@ -1158,4 +1158,4 @@ out_finalize:
#endif
out:
return exit_code;
}
}

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;
@@ -131,7 +143,7 @@ void torture_sftp_close(struct torture_sftp *t);
void torture_write_file(const char *filename, const char *data);
#define torture_filter_tests(tests) \
_torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0])
_torture_filter_tests(tests, ARRAY_SIZE(tests))
void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests);
const char *torture_server_address(int domain);

View File

@@ -24,8 +24,12 @@
#ifndef _TORTURE_KEY_H
#define _TORTURE_KEY_H
#include "config.h"
#include <stdbool.h>
#include "libssh/libssh.h"
#define TORTURE_TESTKEY_PASSWORD "libssh-rocks"
/* Return the encrypted private key in a new OpenSSH format */

View File

@@ -10,20 +10,22 @@
#define LIMIT (8*1024*1024)
static int setup(void **state) {
ssh_buffer buffer;
static int setup(void **state)
{
ssh_buffer buffer = NULL;
buffer = ssh_buffer_new();
if (buffer == NULL) {
return -1;
}
ssh_buffer_set_secure(buffer);
*state = (void *) buffer;
*state = (void *)buffer;
return 0;
}
static int teardown(void **state) {
static int teardown(void **state)
{
SSH_BUFFER_FREE(*state);
return 0;
@@ -33,158 +35,196 @@ static int teardown(void **state) {
* Test if the continuously growing buffer size never exceeds 2 time its
* real capacity
*/
static void torture_growing_buffer(void **state) {
ssh_buffer buffer = *state;
int i;
static void torture_growing_buffer(void **state)
{
ssh_buffer buffer = *state;
int i;
for(i=0;i<LIMIT;++i){
ssh_buffer_add_data(buffer,"A",1);
if(buffer->used >= 128){
if(ssh_buffer_get_len(buffer) * 2 < buffer->allocated){
assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated);
}
for (i = 0; i < LIMIT; ++i) {
ssh_buffer_add_data(buffer, "A", 1);
if (buffer->used >= 128) {
if (ssh_buffer_get_len(buffer) * 2 < buffer->allocated) {
assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated);
}
}
}
}
}
/*
* Test if the continuously growing buffer size never exceeds 2 time its
* real capacity, when we remove 1 byte after each call (sliding window)
*/
static void torture_growing_buffer_shifting(void **state) {
ssh_buffer buffer = *state;
int i;
unsigned char c;
for(i=0; i<1024;++i){
ssh_buffer_add_data(buffer,"S",1);
}
for(i=0;i<LIMIT;++i){
ssh_buffer_get_u8(buffer,&c);
ssh_buffer_add_data(buffer,"A",1);
if(buffer->used >= 128){
if(ssh_buffer_get_len(buffer) * 4 < buffer->allocated){
assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated);
return;
}
static void torture_growing_buffer_shifting(void **state)
{
ssh_buffer buffer = *state;
int i;
unsigned char c;
for (i = 0; i < 1024; ++i) {
ssh_buffer_add_data(buffer, "S", 1);
}
for (i = 0; i < LIMIT; ++i) {
ssh_buffer_get_u8(buffer, &c);
ssh_buffer_add_data(buffer, "A", 1);
if (buffer->used >= 128) {
if (ssh_buffer_get_len(buffer) * 4 < buffer->allocated) {
assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated);
return;
}
}
}
}
}
/*
* Test the behavior of ssh_buffer_prepend_data
*/
static void torture_buffer_prepend(void **state) {
ssh_buffer buffer = *state;
uint32_t v;
ssh_buffer_add_data(buffer,"abcdef",6);
ssh_buffer_prepend_data(buffer,"xyz",3);
assert_int_equal(ssh_buffer_get_len(buffer),9);
assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9);
static void torture_buffer_prepend(void **state)
{
ssh_buffer buffer = *state;
uint32_t v;
/* Now remove 4 bytes and see if we can replace them */
ssh_buffer_get_u32(buffer,&v);
assert_int_equal(ssh_buffer_get_len(buffer),5);
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
ssh_buffer_add_data(buffer, "abcdef", 6);
ssh_buffer_prepend_data(buffer, "xyz", 3);
assert_int_equal(ssh_buffer_get_len(buffer), 9);
assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9);
ssh_buffer_prepend_data(buffer,"aris",4);
assert_int_equal(ssh_buffer_get_len(buffer),9);
assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9);
/* Now remove 4 bytes and see if we can replace them */
ssh_buffer_get_u32(buffer, &v);
assert_int_equal(ssh_buffer_get_len(buffer), 5);
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
/* same thing but we add 5 bytes now */
ssh_buffer_get_u32(buffer,&v);
assert_int_equal(ssh_buffer_get_len(buffer),5);
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
ssh_buffer_prepend_data(buffer, "aris", 4);
assert_int_equal(ssh_buffer_get_len(buffer), 9);
assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9);
ssh_buffer_prepend_data(buffer,"12345",5);
assert_int_equal(ssh_buffer_get_len(buffer),10);
assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10);
/* same thing but we add 5 bytes now */
ssh_buffer_get_u32(buffer, &v);
assert_int_equal(ssh_buffer_get_len(buffer), 5);
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
ssh_buffer_prepend_data(buffer, "12345", 5);
assert_int_equal(ssh_buffer_get_len(buffer), 10);
assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10);
}
/*
* Test the behavior of ssh_buffer_get_ssh_string with invalid data
*/
static void torture_ssh_buffer_get_ssh_string(void **state) {
ssh_buffer buffer;
int i,j,k,l, rc;
/* some values that can go wrong */
uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00,
0x80000000, 0x80000004, 0x7fffffff};
char data[128];
(void)state;
memset(data,'X',sizeof(data));
for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){
for(j=0; j< (int)sizeof(data);++j){
for(k=1;k<5;++k){
buffer = ssh_buffer_new();
assert_non_null(buffer);
static void torture_ssh_buffer_get_ssh_string(void **state)
{
ssh_buffer buffer = NULL;
int i, j, k, l, rc;
/* some values that can go wrong */
uint32_t values[] = {0xffffffff,
0xfffffffe,
0xfffffffc,
0xffffff00,
0x80000000,
0x80000004,
0x7fffffff};
char data[128];
for(l=0;l<k;++l){
rc = ssh_buffer_add_u32(buffer,htonl(values[i]));
assert_int_equal(rc, 0);
(void)state;
memset(data, 'X', sizeof(data));
for (i = 0; i < (int)ARRAY_SIZE(values); ++i) {
for (j = 0; j < (int)sizeof(data); ++j) {
for (k = 1; k < 5; ++k) {
buffer = ssh_buffer_new();
assert_non_null(buffer);
for (l = 0; l < k; ++l) {
rc = ssh_buffer_add_u32(buffer, htonl(values[i]));
assert_int_equal(rc, 0);
}
rc = ssh_buffer_add_data(buffer, data, j);
assert_int_equal(rc, 0);
for (l = 0; l < k; ++l) {
ssh_string str = ssh_buffer_get_ssh_string(buffer);
assert_null(str);
SSH_STRING_FREE(str);
}
SSH_BUFFER_FREE(buffer);
}
}
rc = ssh_buffer_add_data(buffer,data,j);
assert_int_equal(rc, 0);
for(l=0;l<k;++l){
ssh_string str = ssh_buffer_get_ssh_string(buffer);
assert_null(str);
SSH_STRING_FREE(str);
}
SSH_BUFFER_FREE(buffer);
}
}
}
}
static void torture_ssh_buffer_add_format(void **state) {
ssh_buffer buffer=*state;
static void torture_ssh_buffer_add_format(void **state)
{
ssh_buffer buffer = *state;
uint8_t b;
uint16_t w;
uint32_t d;
uint64_t q;
ssh_string s;
ssh_string s = NULL;
int rc;
size_t len;
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06" "libssh"
"\x00\x00\x00\x05" "rocks"
"So much"
"Fun!";
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06"
"libssh"
"\x00\x00\x00\x05"
"rocks"
"So much"
"Fun!";
b=0x42;
w=0x1337;
d=0xbadc0de;
q=0x13243546acbdcedf;
s=ssh_string_from_char("libssh");
rc=ssh_buffer_pack(buffer, "bwdqSsPt",b,w,d,q,s,"rocks",(size_t)7,"So much","Fun!");
b = 0x42;
w = 0x1337;
d = 0xbadc0de;
q = 0x13243546acbdcedf;
s = ssh_string_from_char("libssh");
rc = ssh_buffer_pack(buffer,
"bwdqSsPt",
b,
w,
d,
q,
s,
"rocks",
(size_t)7,
"So much",
"Fun!");
assert_int_equal(rc, SSH_OK);
len = ssh_buffer_get_len(buffer);
assert_int_equal(len, sizeof(verif) - 1);
assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1);
assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) - 1);
SSH_STRING_FREE(s);
}
static void torture_ssh_buffer_get_format(void **state) {
ssh_buffer buffer=*state;
uint8_t b=0;
uint16_t w=0;
uint32_t d=0;
uint64_t q=0;
ssh_string s=NULL;
char *s1=NULL, *s2=NULL;
static void torture_ssh_buffer_get_format(void **state)
{
ssh_buffer buffer = *state;
uint8_t b = 0;
uint16_t w = 0;
uint32_t d = 0;
uint64_t q = 0;
ssh_string s = NULL;
char *s1 = NULL, *s2 = NULL;
int rc;
size_t len;
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06" "libssh"
"\x00\x00\x00\x05" "rocks"
"So much";
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06"
"libssh"
"\x00\x00\x00\x05"
"rocks"
"So much";
rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1);
assert_int_equal(rc, SSH_OK);
rc = ssh_buffer_unpack(buffer, "bwdqSsP",&b,&w,&d,&q,&s,&s1,(size_t)7,&s2);
rc = ssh_buffer_unpack(buffer,
"bwdqSsP",
&b,
&w,
&d,
&q,
&s,
&s1,
(size_t)7,
&s2);
assert_int_equal(rc, SSH_OK);
assert_int_equal(b, 0x42);
@@ -210,24 +250,37 @@ static void torture_ssh_buffer_get_format(void **state) {
SAFE_FREE(s2);
}
static void torture_ssh_buffer_get_format_error(void **state) {
ssh_buffer buffer=*state;
uint8_t b=0;
uint16_t w=0;
uint32_t d=0;
uint64_t q=0;
ssh_string s=NULL;
char *s1=NULL, *s2=NULL;
static void torture_ssh_buffer_get_format_error(void **state)
{
ssh_buffer buffer = *state;
uint8_t b = 0;
uint16_t w = 0;
uint32_t d = 0;
uint64_t q = 0;
ssh_string s = NULL;
char *s1 = NULL, *s2 = NULL;
int rc;
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06" "libssh"
"\x00\x00\x00\x05" "rocks"
"So much";
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
"\xac\xbd\xce\xdf"
"\x00\x00\x00\x06"
"libssh"
"\x00\x00\x00\x05"
"rocks"
"So much";
rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1);
assert_int_equal(rc, SSH_OK);
rc = ssh_buffer_unpack(buffer, "bwdqSsPb",&b,&w,&d,&q,&s,&s1,(size_t)7,&s2,&b);
rc = ssh_buffer_unpack(buffer,
"bwdqSsPb",
&b,
&w,
&d,
&q,
&s,
&s1,
(size_t)7,
&s2,
&b);
assert_int_equal(rc, SSH_ERROR);
assert_null(s);
@@ -235,7 +288,8 @@ static void torture_ssh_buffer_get_format_error(void **state) {
assert_null(s2);
}
static void torture_buffer_pack_badformat(void **state){
static void torture_buffer_pack_badformat(void **state)
{
ssh_buffer buffer = *state;
uint8_t b = 42;
int rc;

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

@@ -130,23 +130,23 @@ extern LIBSSH_THREAD int ssh_log_level;
"ProxyJump = many-spaces.com\n" /* valid */
/* Match keyword */
#define LIBSSH_TESTCONFIG_STRING10 \
"Match host example\n" \
"\tHostName example.com\n" \
"Match host example1,example2\n" \
"\tHostName exampleN\n" \
"Match user guest\n" \
"\tHostName guest.com\n" \
"Match user tester host testhost\n" \
"\tHostName testhost.com\n" \
#define LIBSSH_TESTCONFIG_STRING10 \
"Match host example\n" \
"\tHostName example.com\n" \
"Match host example1,example2\n" \
"\tHostName exampleN\n" \
"Match user guest\n" \
"\tHostName guest.com\n" \
"Match user tester host testhost\n" \
"\tHostName testhost.com\n" \
"Match !user tester host testhost\n" \
"\tHostName nonuser-testhost.com\n" \
"Match all\n" \
"\tHostName all-matched.com\n" \
/* Unsupported options */ \
"Match originalhost example\n" \
"\tHostName original-example.com\n" \
"Match localuser guest\n" \
"\tHostName nonuser-testhost.com\n" \
"Match all\n" \
"\tHostName all-matched.com\n" \
"Match originalhost example\n" \
"\tHostName original-example.com\n" \
"\tUser originaluser\n" \
"Match localuser guest\n" \
"\tHostName local-guest.com\n"
/* ProxyJump */
@@ -851,26 +851,40 @@ static void torture_config_match(void **state,
ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "all-matched.com");
assert_string_equal(session->opts.originalhost, "unmatched");
/* Hostname example does simple hostname matching */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "example.com");
assert_string_equal(session->opts.originalhost, "example");
/* We can match also both hosts from a comma separated list */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "example1");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "exampleN");
assert_string_equal(session->opts.originalhost, "example1");
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "example2");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "exampleN");
assert_string_equal(session->opts.originalhost, "example2");
/* We can match by user */
/* We can match by originalhost */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "example.com");
assert_string_equal(session->opts.originalhost, "example");
/* Match originalhost sets User */
assert_string_equal(session->opts.username, "originaluser");
/* We can match by user - clear originalhost to isolate user match */
torture_reset_config(session);
SAFE_FREE(session->opts.originalhost);
ssh_options_set(session, SSH_OPTIONS_USER, "guest");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "guest.com");
@@ -881,6 +895,7 @@ static void torture_config_match(void **state,
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "testhost.com");
assert_string_equal(session->opts.originalhost, "testhost");
/* We can also negate conditions */
torture_reset_config(session);
@@ -888,9 +903,43 @@ static void torture_config_match(void **state,
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "nonuser-testhost.com");
assert_string_equal(session->opts.originalhost, "testhost");
/* In this part, we try various other config files and strings. */
/* Match host compares against resolved hostname */
config = "Host ssh-host\n"
"\tHostname 10.1.1.1\n"
"Match host 10.1.1.*\n"
"\tPort 2222\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
session->opts.port = 0;
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "10.1.1.1");
assert_string_equal(session->opts.originalhost, "ssh-host");
assert_int_equal(session->opts.port, 2222);
/* Match host falls back to originalhost when host is NULL */
config = "Match host my_alias\n"
"\tHostName alias-matched.com\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
SAFE_FREE(session->opts.username);
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
assert_null(session->opts.host);
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "alias-matched.com");
/* Match final is not completely supported, but should do quite much the
* same as "match all". The trailing "all" is not mandatory. */
config = "Match final all\n"
@@ -1018,7 +1067,7 @@ static void torture_config_match(void **state,
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.host, "unmatched");
/* Missing argument to unsupported option originalhost */
/* Missing argument to option originalhost */
config = "Match originalhost\n"
"\tHost originalhost.com\n";
if (file != NULL) {
@@ -1289,7 +1338,6 @@ static void torture_config_proxyjump(void **state,
assert_string_equal(session->opts.ProxyCommand,
"ssh -W '[%h]:%p' 2620:52:0::fed");
/* Multiple @ is allowed in second jump */
config = "Host allowed-hostname\n"
"\tProxyJump localhost,user@principal.com@jumpbox:22\n";
@@ -1351,7 +1399,73 @@ static void torture_config_proxyjump(void **state,
"jumpbox",
"user@principal.com",
"22");
/* Non-RFC-1035 alias (underscore) — accepted with non-strict parse */
config = "Host alias-jump\n"
"\tProxyJump my_alias\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
_parse_config(session, file, string, SSH_OK);
helper_proxy_jump_check(session->opts.proxy_jumps->root,
"my_alias",
NULL,
NULL);
/* Non-RFC-1035 alias in multi-hop second jump */
config = "Host alias-multi\n"
"\tProxyJump localhost,my_alias:2222\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
_parse_config(session, file, string, SSH_OK);
helper_proxy_jump_check(session->opts.proxy_jumps->root,
"my_alias",
NULL,
"2222");
helper_proxy_jump_check(session->opts.proxy_jumps->root->next,
"localhost",
NULL,
NULL);
/* Non-RFC-1035 alias — proxycommand based */
torture_setenv("OPENSSH_PROXYJUMP", "1");
config = "Host alias-jump\n"
"\tProxyJump my_alias\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.ProxyCommand,
"ssh -W '[%h]:%p' my_alias");
/* Non-RFC-1035 alias in multi-hop — proxycommand based */
config = "Host alias-multi\n"
"\tProxyJump localhost,my_alias:2222\n";
if (file != NULL) {
torture_write_file(file, config);
} else {
string = config;
}
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
_parse_config(session, file, string, SSH_OK);
assert_string_equal(session->opts.ProxyCommand,
"ssh -J my_alias:2222 -W '[%h]:%p' localhost");
torture_unsetenv("OPENSSH_PROXYJUMP");
/* In this part, we try various other config files and strings. */
torture_setenv("OPENSSH_PROXYJUMP", "1");
@@ -2762,21 +2876,36 @@ static void torture_config_parse_uri(void **state)
(void)state; /* unused */
rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false);
rc = ssh_config_parse_uri("localhost",
&username,
&hostname,
&port,
false,
true);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "localhost");
SAFE_FREE(hostname);
assert_null(port);
rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false);
rc = ssh_config_parse_uri("1.2.3.4",
&username,
&hostname,
&port,
false,
true);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "1.2.3.4");
SAFE_FREE(hostname);
assert_null(port);
rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false);
rc = ssh_config_parse_uri("1.2.3.4:2222",
&username,
&hostname,
&port,
false,
true);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "1.2.3.4");
@@ -2784,7 +2913,12 @@ static void torture_config_parse_uri(void **state)
assert_string_equal(port, "2222");
SAFE_FREE(port);
rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false);
rc = ssh_config_parse_uri("[1:2:3::4]:2222",
&username,
&hostname,
&port,
false,
true);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "1:2:3::4");
@@ -2793,13 +2927,64 @@ static void torture_config_parse_uri(void **state)
SAFE_FREE(port);
/* do not want port */
rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true);
rc = ssh_config_parse_uri("1:2:3::4",
&username,
&hostname,
NULL,
true,
true);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "1:2:3::4");
SAFE_FREE(hostname);
rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true);
rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true, true);
assert_int_equal(rc, SSH_ERROR);
/* Non-strict accepts non-RFC1035 chars (e.g. _, %) */
rc = ssh_config_parse_uri("customer_1",
&username,
&hostname,
NULL,
true,
false);
assert_return_code(rc, errno);
assert_null(username);
assert_string_equal(hostname, "customer_1");
SAFE_FREE(hostname);
rc = ssh_config_parse_uri("admin@%prod",
&username,
&hostname,
NULL,
true,
false);
assert_return_code(rc, errno);
assert_string_equal(username, "admin");
assert_string_equal(hostname, "%prod");
SAFE_FREE(username);
SAFE_FREE(hostname);
/* Strict rejects what non-strict accepts */
rc = ssh_config_parse_uri("customer_1",
&username,
&hostname,
NULL,
true,
true);
assert_int_equal(rc, SSH_ERROR);
/* Non-strict rejects shell metacharacters */
rc = ssh_config_parse_uri("host;cmd",
&username,
&hostname,
NULL,
true,
false);
assert_int_equal(rc, SSH_ERROR);
/* Non-strict rejects leading dash */
rc = ssh_config_parse_uri("-host", &username, &hostname, NULL, true, false);
assert_int_equal(rc, SSH_ERROR);
}
@@ -2933,6 +3118,48 @@ static void torture_config_jump(void **state)
printf("%s: EOF\n", __func__);
}
/* Verify Hostname directive resolves host without overwriting originalhost
*/
static void torture_config_hostname(void **state)
{
ssh_session session = *state;
char *expanded = NULL;
/* Hostname directive sets host, originalhost is unchanged */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
assert_null(session->opts.host);
assert_string_equal(session->opts.originalhost, "my_alias");
_parse_config(session,
NULL,
"Host my_alias\n\tHostname 192.168.1.1\n",
SSH_OK);
assert_string_equal(session->opts.host, "192.168.1.1");
assert_string_equal(session->opts.originalhost, "my_alias");
/* Host keyword compares against originalhost, not the resolved IP */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
_parse_config(session,
NULL,
"Host ssh-host\n\tHostname 10.1.1.1\n"
"Host 10.1.1.*\n\tProxyJump ssh-host\n",
SSH_OK);
assert_string_equal(session->opts.host, "10.1.1.1");
assert_string_equal(session->opts.originalhost, "ssh-host");
assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0);
assert_null(session->opts.ProxyCommand);
/* %h falls back to originalhost when host is not yet resolved */
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
assert_null(session->opts.host);
expanded = ssh_path_expand_escape(session, "%h");
assert_non_null(expanded);
assert_string_equal(expanded, "my_alias");
free(expanded);
}
/* Invalid configuration files
*/
static void torture_config_invalid(void **state)
@@ -3101,7 +3328,8 @@ int torture_run_tests(void)
cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value,
setup,
teardown),
cmocka_unit_test_setup_teardown(torture_config_jump,
cmocka_unit_test_setup_teardown(torture_config_jump, setup, teardown),
cmocka_unit_test_setup_teardown(torture_config_hostname,
setup,
teardown),
cmocka_unit_test_setup_teardown(torture_config_invalid,

View File

@@ -584,7 +584,7 @@ torture_match_cidr_address_list_ipv6(void **state)
(void)state;
/* Test valid link-local addresses */
valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]);
valid_addr_len = ARRAY_SIZE(valid_addr);
for (i = 0; i < valid_addr_len; i++) {
rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6);
assert_int_equal(rc, 1);
@@ -601,7 +601,7 @@ torture_match_cidr_address_list_ipv6(void **state)
assert_int_equal(rc, 1);
/* Test some invalid input */
invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]);
invalid_addr_len = ARRAY_SIZE(invalid_addr);
for (i = 0; i < invalid_addr_len; i++) {
rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6);
assert_int_equal(rc, 0);

View File

@@ -17,6 +17,13 @@
#include <libssh/pki.h>
#include <libssh/pki_priv.h>
#include <libssh/session.h>
#ifdef _WIN32
#include <netioapi.h>
#else
#include <net/if.h>
#endif
#ifdef WITH_SERVER
#include <libssh/bind.h>
#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config"
@@ -59,12 +66,16 @@ static void torture_options_set_host(void **state) {
assert_true(rc == 0);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "localhost");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "localhost");
/* IPv4 address */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1");
assert_true(rc == 0);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "127.1.1.1");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "127.1.1.1");
assert_null(session->opts.username);
/* IPv6 address */
@@ -72,12 +83,16 @@ static void torture_options_set_host(void **state) {
assert_true(rc == 0);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "::1");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "::1");
assert_null(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation");
assert_true(rc == 0);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "meditation");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "meditation");
assert_non_null(session->opts.username);
assert_string_equal(session->opts.username, "guru");
@@ -86,6 +101,8 @@ static void torture_options_set_host(void **state) {
assert_true(rc == 0);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "hostname");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "hostname");
assert_non_null(session->opts.username);
assert_string_equal(session->opts.username, "at@login");
@@ -104,6 +121,9 @@ static void torture_options_set_host(void **state) {
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host,
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost,
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
assert_null(session->opts.username);
/* IPv6 hostnames should work also with square braces */
@@ -114,20 +134,103 @@ static void torture_options_set_host(void **state) {
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host,
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost,
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
assert_null(session->opts.username);
/* user@IPv6%interface
* Use dynamic interface name for cross-platform portability */
{
char interf[IF_NAMESIZE] = {0};
char ipv6_zone[128] = {0};
char expected_host[128] = {0};
if_indextoname(1, interf);
assert_non_null(interf);
snprintf(ipv6_zone, sizeof(ipv6_zone), "user@fe80::1%%%s", interf);
snprintf(expected_host, sizeof(expected_host), "fe80::1%%%s", interf);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, ipv6_zone);
assert_return_code(rc, errno);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, expected_host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, expected_host);
assert_string_equal(session->opts.username, "user");
}
/* IDN need to be in punycode format */
SAFE_FREE(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld");
assert_return_code(rc, errno);
assert_non_null(session->opts.host);
assert_string_equal(session->opts.host, "xn--bcher-kva.tld");
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "xn--bcher-kva.tld");
assert_null(session->opts.username);
/* IDN in UTF8 won't work */
/* IDN in UTF-8 is accepted but not as a valid hostname,
* only originalhost is set */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld");
assert_string_equal(ssh_get_error(session),
"Invalid argument in ssh_options_set");
assert_return_code(rc, errno);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "bücher.tld");
/* Config alias '%' rejected by RFC1035, only originalhost is set */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "%customer1");
assert_return_code(rc, errno);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "%customer1");
/* user@alias '_' rejected by RFC1035, alias stored in originalhost */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "admin@customer_1");
assert_return_code(rc, errno);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_string_equal(session->opts.username, "admin");
/* Shell metacharacters and leading dash rejected.
* Verify failure cases do not update session options. */
SAFE_FREE(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "host;rm -rf /");
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_null(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "-leading-dash");
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_null(session->opts.username);
/* Empty user or host rejected */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "@hostname");
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_null(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "user@");
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_null(session->opts.username);
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "");
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_string_equal(session->opts.originalhost, "customer_1");
assert_null(session->opts.username);
}
static void torture_options_set_ciphers(void **state)
@@ -714,6 +817,26 @@ static void torture_options_get_host(void **state)
assert_string_equal(host, "localhost");
ssh_string_free_char(host);
/* When host is not yet resolved, falls back to originalhost */
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
assert_true(rc == 0);
assert_null(session->opts.host);
assert_non_null(session->opts.originalhost);
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
assert_string_equal(host, "my_alias");
ssh_string_free_char(host);
/* After config resolution, get returns resolved host, not originalhost */
session->opts.host = strdup("192.168.1.1");
assert_non_null(session->opts.host);
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
assert_string_equal(host, "192.168.1.1");
ssh_string_free_char(host);
/* originalhost is unchanged */
assert_string_equal(session->opts.originalhost, "my_alias");
}
static void torture_options_set_port(void **state)
@@ -1074,6 +1197,7 @@ static void torture_options_config_host(void **state)
{
ssh_session session = *state;
FILE *config = NULL;
int rv;
/* create a new config file */
config = fopen("test_config", "w");
@@ -1113,6 +1237,33 @@ static void torture_options_config_host(void **state)
ssh_options_parse_config(session, "test_config");
assert_int_equal(session->opts.port, 44);
/* ssh_options_parse_config rejects when originalhost is NULL */
SAFE_FREE(session->opts.host);
SAFE_FREE(session->opts.originalhost);
rv = ssh_options_parse_config(session, "test_config");
assert_int_equal(rv, -1);
/* Config Hostname with invalid hostname: verify stale host not leaked */
torture_write_file("test_config", "Host 192.168.1.1\nHostname my_alias\n");
torture_reset_config(session);
ssh_options_set(session, SSH_OPTIONS_HOST, "192.168.1.1");
assert_string_equal(session->opts.host, "192.168.1.1");
assert_string_equal(session->opts.originalhost, "192.168.1.1");
rv = ssh_options_parse_config(session, "test_config");
assert_int_equal(rv, 0);
assert_null(session->opts.host);
assert_string_equal(session->opts.originalhost, "192.168.1.1");
/* Calling ssh_options_set(HOST) twice: verify stale host not leaked */
ssh_options_set(session, SSH_OPTIONS_HOST, "real.server.com");
assert_string_equal(session->opts.host, "real.server.com");
assert_string_equal(session->opts.originalhost, "real.server.com");
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
assert_null(session->opts.host);
assert_string_equal(session->opts.originalhost, "my_alias");
unlink("test_config");
}
@@ -1437,6 +1588,7 @@ static void torture_options_copy(void **state)
assert_string_equal(session->opts.username, new->opts.username);
assert_string_equal(session->opts.host, new->opts.host);
assert_string_equal(session->opts.originalhost, new->opts.originalhost);
assert_string_equal(session->opts.bindaddr, new->opts.bindaddr);
assert_string_equal(session->opts.sshdir, new->opts.sshdir);
assert_string_equal(session->opts.knownhosts, new->opts.knownhosts);

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

View File

@@ -647,7 +647,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
ssh_session session=ssh_new();
(void) state;
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key);
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P256, NULL, &key);
assert_return_code(rc, errno);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
@@ -690,7 +690,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key);
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P384, NULL, &key);
assert_return_code(rc, errno);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
@@ -733,7 +733,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key);
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P521, NULL, &key);
assert_return_code(rc, errno);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);

View File

@@ -546,7 +546,7 @@ static void torture_pki_ed25519_generate_key(void **state)
assert_non_null(session);
rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 256, &key);
rc = ssh_pki_generate_key(SSH_KEYTYPE_ED25519, NULL, &key);
assert_true(rc == SSH_OK);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);

View File

@@ -440,7 +440,7 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state)
SSH_KEY_FREE(pubkey);
/* Generate different key and try to assign it this certificate */
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &privkey);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &privkey);
assert_return_code(rc, errno);
assert_non_null(privkey);
rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey);
@@ -651,8 +651,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
ssh_signature_free(sign);
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
key = NULL;
pubkey = NULL;
}
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
@@ -668,8 +666,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
ssh_signature_free(sign);
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
key = NULL;
pubkey = NULL;
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key);
assert_return_code(rc, errno);
@@ -684,8 +680,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
ssh_signature_free(sign);
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
key = NULL;
pubkey = NULL;
ssh_free(session);
}
@@ -760,15 +754,33 @@ static void torture_pki_rsa_sha2(void **state)
static void torture_pki_rsa_key_size(void **state)
{
int rc;
int rc, bit_size;
ssh_key key = NULL, pubkey = NULL;
ssh_signature sign = NULL;
ssh_session session=ssh_new();
unsigned int length = 4096;
ssh_pki_ctx ctx = NULL;
(void) state;
(void)state;
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
ctx = ssh_pki_ctx_new();
assert_non_null(ctx);
/* Invalid argument NULL */
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, NULL);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
/* Too small size */
bit_size = 768;
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
/* Ok value */
bit_size = 2048;
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
assert_return_code(rc, errno);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_return_code(rc, errno);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
@@ -790,9 +802,7 @@ static void torture_pki_rsa_key_size(void **state)
ssh_signature_free(sign);
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
key = NULL;
pubkey = NULL;
SSH_PKI_CTX_FREE(ctx);
ssh_free(session);
}
@@ -890,11 +900,19 @@ static void torture_pki_sign_data_rsa(void **state)
{
int rc;
ssh_key key = NULL;
ssh_pki_ctx ctx = NULL;
int bit_size = 2048;
(void) state;
/* Setup */
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
ctx = ssh_pki_ctx_new();
assert_non_null(ctx);
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
assert_int_equal(rc, SSH_OK);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_int_equal(rc, SSH_OK);
assert_non_null(key);
@@ -914,6 +932,7 @@ static void torture_pki_sign_data_rsa(void **state)
/* Cleanup */
SSH_KEY_FREE(key);
SSH_PKI_CTX_FREE(ctx);
}
static void torture_pki_fail_sign_with_incompatible_hash(void **state)
@@ -921,12 +940,20 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state)
int rc;
ssh_key key = NULL;
ssh_key pubkey = NULL;
ssh_pki_ctx ctx = NULL;
int bit_size = 2048;
ssh_signature sig, bad_sig;
(void) state;
/* Setup */
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
ctx = ssh_pki_ctx_new();
assert_non_null(ctx);
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
assert_int_equal(rc, SSH_OK);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_int_equal(rc, SSH_OK);
assert_non_null(key);
@@ -956,6 +983,7 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state)
ssh_signature_free(sig);
SSH_KEY_FREE(pubkey);
SSH_KEY_FREE(key);
SSH_PKI_CTX_FREE(ctx);
}
static void

View File

@@ -142,8 +142,7 @@ static int setup_sshsig_compat(void **state)
test_state->original_cwd = original_cwd;
test_state->temp_dir = temp_dir;
test_state->test_combinations = test_combinations;
test_state->num_combinations =
sizeof(test_combinations) / sizeof(test_combinations[0]);
test_state->num_combinations = ARRAY_SIZE(test_combinations);
*state = test_state;

View File

@@ -550,14 +550,23 @@ static void *thread_pki_rsa_generate_key(void *threadid)
ssh_key key = NULL, pubkey = NULL;
ssh_signature sign = NULL;
ssh_session session = NULL;
ssh_pki_ctx ctx = NULL;
int size = 0;
(void) threadid;
session = ssh_new();
assert_non_null(session);
ctx = ssh_pki_ctx_new();
assert_non_null(ctx);
if (!ssh_fips_mode()) {
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key);
size = 1024;
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
assert_return_code(rc, errno);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_ssh_return_code(session, rc);
assert_non_null(key);
@@ -576,7 +585,11 @@ static void *thread_pki_rsa_generate_key(void *threadid)
SSH_KEY_FREE(pubkey);
}
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
size = 2048;
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
assert_return_code(rc, errno);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_ssh_return_code(session, rc);
assert_non_null(key);
@@ -594,8 +607,12 @@ static void *thread_pki_rsa_generate_key(void *threadid)
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key);
assert_true(rc == SSH_OK);
size = 4096;
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
assert_return_code(rc, errno);
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
assert_ssh_return_code(session, rc);
assert_non_null(key);
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
@@ -612,6 +629,7 @@ static void *thread_pki_rsa_generate_key(void *threadid)
SSH_KEY_FREE(key);
SSH_KEY_FREE(pubkey);
SSH_PKI_CTX_FREE(ctx);
ssh_free(session);
return NULL;

View File

@@ -421,3 +421,95 @@
fun:gss_acquire_cred_from
fun:gss_acquire_cred
}
# Function mecherror_copy called in various
# functions of the krb5 library copies entries
# to the global error mapping table (mecherrmap m).
{
Global error mapping table in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
fun:mecherror_copy
}
# Function add_error_table called in various
# functions of the krb5 library adds entries
# to a global list of error tables et_list.
{
Global list of error tables in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
fun:add_error_table
}
# Function build_mechSet builds the global
# gss_OID_set_desc g_mechSet which is only
# free'd when initialized again.
{
Global OID set in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:build_mechSet
}
# Function gssint_register_mechinfo()
# called from gssint_mechglue_init() adds
# entries to a global linked list g_mechList.
{
Global list of gss_mech_info in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:gssint_register_mechinfo*
...
fun:gssint_mechglue_init
}
# Function addConfigEntry() called during
# updateMechList() adds entries to
# a global linked list g_mechList.
{
Global list of gss_mech_info in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:addConfigEntry
...
fun:updateMechList
}
# Function loadInterMech() called during
# updateMechList() loops through the global
# linked list g_mechList and updates its entries
# with heap-alloced "interposer fields".
{
Global list of gss_mech_info in krb5
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:loadInterMech
...
fun:updateMechList
}
# Multiple krb5 functions call krb5int_open_plugin
# which opens shared libraries using dlopen.
# The plugin handle then seems to be stored in the
# main krb5 context.
{
Plugin handles stored in the krb5 context
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
...
fun:dlopen*
...
fun:krb5int_open_plugin
}