mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-03-24 20:40:09 +09:00
Compare commits
51 Commits
libssh-0.1
...
a05b2b76be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a05b2b76be | ||
|
|
c9f34ac55f | ||
|
|
bc24bba176 | ||
|
|
3154a4ab8d | ||
|
|
9478de8082 | ||
|
|
e927820082 | ||
|
|
67950c620d | ||
|
|
31ea4d1213 | ||
|
|
29c503ed7c | ||
|
|
b1a28f7987 | ||
|
|
616d165f14 | ||
|
|
b9ecb9283e | ||
|
|
c38edb59f2 | ||
|
|
def7a679f8 | ||
|
|
6f671919ad | ||
|
|
45b1d85fb0 | ||
|
|
e7f4cc9580 | ||
|
|
5479b276b2 | ||
|
|
5d7fbcf22a | ||
|
|
123c442a56 | ||
|
|
4dfcdd96b8 | ||
|
|
9d36b9dd81 | ||
|
|
afa21334b4 | ||
|
|
a2ebc7ea9b | ||
|
|
1ab8a35c5d | ||
|
|
8782fcec18 | ||
|
|
8d563f90f3 | ||
|
|
6a5e298cec | ||
|
|
163e1b059b | ||
|
|
e16018491e | ||
|
|
c26e9298e3 | ||
|
|
3c0567cb67 | ||
|
|
00d1903bf6 | ||
|
|
bc2a483aa1 | ||
|
|
5ad8dda6f6 | ||
|
|
d680b8ea8a | ||
|
|
90b07e2c18 | ||
|
|
edbd929fa2 | ||
|
|
38932b74c0 | ||
|
|
60d6179eaa | ||
|
|
0d9b2c68cc | ||
|
|
adc2462329 | ||
|
|
0bff33c790 | ||
|
|
47e9b5536a | ||
|
|
2f1f474e27 | ||
|
|
18d7a3967c | ||
|
|
d45ce10c83 | ||
|
|
a7fd80795e | ||
|
|
f8cba20859 | ||
|
|
f13a8d7ced | ||
|
|
c0963b3417 |
11
CHANGELOG
11
CHANGELOG
@@ -2,6 +2,17 @@ CHANGELOG
|
||||
=========
|
||||
|
||||
version 0.12.0 (released 2026-02-10)
|
||||
* Security:
|
||||
* CVE-2025-14821: libssh loads configuration files from the C:\etc directory
|
||||
on Windows
|
||||
* CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request()
|
||||
* CVE-2026-0965: Possible Denial of Service when parsing unexpected
|
||||
configuration files
|
||||
* CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input
|
||||
* CVE-2026-0967: Specially crafted patterns could cause DoS
|
||||
* CVE-2026-0968: OOB Read in sftp_parse_longname()
|
||||
* libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP
|
||||
extensions
|
||||
* Deprecations and removals:
|
||||
* Bumped minimal RSA key size to 1024 bits
|
||||
* New functionality:
|
||||
|
||||
@@ -108,7 +108,6 @@ if (DOXYGEN_FOUND)
|
||||
packet_struct,
|
||||
pem_get_password_struct,
|
||||
ssh_tokens_st,
|
||||
sftp_attributes_struct,
|
||||
sftp_client_message_struct,
|
||||
sftp_dir_struct,
|
||||
sftp_ext_struct,
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -337,6 +337,7 @@ static void batch_shell(ssh_session session)
|
||||
static int client(ssh_session session)
|
||||
{
|
||||
int auth = 0;
|
||||
int authenticated = 0;
|
||||
char *banner = NULL;
|
||||
int state;
|
||||
|
||||
@@ -369,16 +370,28 @@ static int client(ssh_session session)
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssh_userauth_none(session, NULL);
|
||||
banner = ssh_get_issue_banner(session);
|
||||
if (banner) {
|
||||
printf("%s\n", banner);
|
||||
free(banner);
|
||||
}
|
||||
auth = authenticate_console(session);
|
||||
if (auth != SSH_AUTH_SUCCESS) {
|
||||
auth = ssh_userauth_none(session, NULL);
|
||||
if (auth == SSH_AUTH_SUCCESS) {
|
||||
authenticated = 1;
|
||||
} else if (auth == SSH_AUTH_ERROR) {
|
||||
fprintf(stderr,
|
||||
"Authentication error during none auth: %s\n",
|
||||
ssh_get_error(session));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
auth = authenticate_console(session);
|
||||
if (auth != SSH_AUTH_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmds[0] == NULL) {
|
||||
shell(session);
|
||||
} else {
|
||||
|
||||
@@ -107,6 +107,14 @@ static struct argp_option options[] = {
|
||||
.doc = "Set the authorized keys file.",
|
||||
.group = 0
|
||||
},
|
||||
{
|
||||
.name = "option",
|
||||
.key = 'o',
|
||||
.arg = "OPTION",
|
||||
.flags = 0,
|
||||
.doc = "Set server configuration option [-o OptionName=Value]",
|
||||
.group = 0
|
||||
},
|
||||
{
|
||||
.name = "user",
|
||||
.key = 'u',
|
||||
@@ -158,6 +166,9 @@ parse_opt(int key, char *arg, struct argp_state *state)
|
||||
case 'a':
|
||||
strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1);
|
||||
break;
|
||||
case 'o':
|
||||
ssh_bind_config_parse_string(sshbind, arg);
|
||||
break;
|
||||
case 'u':
|
||||
strncpy(username, arg, sizeof(username) - 1);
|
||||
break;
|
||||
@@ -194,7 +205,7 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
{
|
||||
int key;
|
||||
|
||||
while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) {
|
||||
while((key = getopt(argc, argv, "a:e:k:o:p:P:r:u:v")) != -1) {
|
||||
if (key == 'p') {
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg);
|
||||
} else if (key == 'k') {
|
||||
@@ -205,6 +216,8 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg);
|
||||
} else if (key == 'a') {
|
||||
strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1);
|
||||
} else if (key == 'o') {
|
||||
ssh_bind_config_parse_string(sshbind, optarg);
|
||||
} else if (key == 'u') {
|
||||
strncpy(username, optarg, sizeof(username) - 1);
|
||||
} else if (key == 'P') {
|
||||
@@ -222,6 +235,7 @@ parse_opt(int argc, char **argv, ssh_bind sshbind)
|
||||
"libssh %s -- a Secure Shell protocol implementation\n"
|
||||
"\n"
|
||||
" -a, --authorizedkeys=FILE Set the authorized keys file.\n"
|
||||
" -o, --option=OPTION Set server configuration option (e.g., -o OptionName=Value).\n"
|
||||
" -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n"
|
||||
" -k, --hostkey=FILE Set a host key. Can be used multiple times.\n"
|
||||
" Implies no default keys.\n"
|
||||
|
||||
@@ -74,6 +74,13 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief SSH agent connection context.
|
||||
*
|
||||
* This structure represents a connection to an SSH authentication agent.
|
||||
* It is used by libssh to communicate with the agent for operations such
|
||||
* as listing available identities and signing data during authentication.
|
||||
*/
|
||||
struct ssh_agent_struct {
|
||||
struct ssh_socket_struct *sock;
|
||||
ssh_buffer ident;
|
||||
@@ -82,13 +89,24 @@ struct ssh_agent_struct {
|
||||
};
|
||||
|
||||
/* agent.c */
|
||||
|
||||
/**
|
||||
* @brief Create a new ssh agent structure.
|
||||
*
|
||||
* @return An allocated ssh agent structure or NULL on error.
|
||||
* Creates and initializes an SSH agent context bound to the given
|
||||
* SSH session. The agent connection relies on the session configuration
|
||||
* (e.g. agent forwarding).
|
||||
*
|
||||
* @param session The SSH session the agent will be associated with.
|
||||
*
|
||||
* @return An allocated ssh agent structure on success, or NULL on error.
|
||||
*
|
||||
* @note This function does not start or manage an external agent
|
||||
* process; it only connects to an already running agent.
|
||||
*/
|
||||
struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session);
|
||||
|
||||
|
||||
void ssh_agent_close(struct ssh_agent_struct *agent);
|
||||
|
||||
/**
|
||||
@@ -101,23 +119,75 @@ void ssh_agent_free(struct ssh_agent_struct *agent);
|
||||
/**
|
||||
* @brief Check if the ssh agent is running.
|
||||
*
|
||||
* @param session The ssh session to check for the agent.
|
||||
* @param session The SSH session to check for agent availability.
|
||||
*
|
||||
* @return 1 if it is running, 0 if not.
|
||||
* @return 1 if an agent is available, 0 otherwise.
|
||||
*/
|
||||
int ssh_agent_is_running(struct ssh_session_struct *session);
|
||||
|
||||
uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session);
|
||||
|
||||
ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the first identity provided by the SSH agent.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param comment Optional pointer to receive the key comment.
|
||||
*
|
||||
* @return A public key on success, or NULL if no identities are available.
|
||||
*
|
||||
* @note The returned key is owned by the caller and must be freed
|
||||
* using ssh_key_free().
|
||||
*/
|
||||
ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the next identity provided by the SSH agent.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param comment Optional pointer to receive the key comment.
|
||||
*
|
||||
* @return A public key on success, or NULL if no further identities exist.
|
||||
*
|
||||
* @note The returned key is owned by the caller and must be freed
|
||||
* using ssh_key_free().
|
||||
*/
|
||||
ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
|
||||
char **comment);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Request the SSH agent to sign data using a public key.
|
||||
*
|
||||
* Asks the SSH agent to generate a signature over the provided data
|
||||
* using the specified public key.
|
||||
*
|
||||
* @param session The SSH session associated with the agent.
|
||||
* @param pubkey The public key identifying the signing identity.
|
||||
* @param data The data to be signed.
|
||||
*
|
||||
* @return A newly allocated ssh_string containing the signature on
|
||||
* success, or NULL on failure.
|
||||
*
|
||||
* @note This operation requires that the agent possesses the
|
||||
* corresponding private key and may prompt the user for
|
||||
* confirmation depending on agent configuration.
|
||||
*/
|
||||
ssh_string ssh_agent_sign_data(ssh_session session,
|
||||
const ssh_key pubkey,
|
||||
struct ssh_buffer_struct *data);
|
||||
/**
|
||||
* @brief Remove an identity from the SSH agent.
|
||||
*
|
||||
* @param session The SSH session.
|
||||
* @param key The public key to remove.
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
|
||||
*/
|
||||
int ssh_agent_remove_identity(ssh_session session,
|
||||
const ssh_key key);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -57,25 +57,16 @@ 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
|
||||
*/
|
||||
int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename);
|
||||
|
||||
/* @brief Parse configuration string and set the options to the given bind session
|
||||
*
|
||||
* @params[in] bind The ssh bind session
|
||||
* @params[in] input Null terminated string containing the configuration
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
*/
|
||||
int ssh_bind_config_parse_string(ssh_bind bind, const char *input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#ifndef _BLF_H_
|
||||
#define _BLF_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//#include "includes.h"
|
||||
|
||||
#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H)
|
||||
|
||||
@@ -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)]))
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ Public domain.
|
||||
#ifndef CHACHA_H
|
||||
#define CHACHA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct chacha_ctx {
|
||||
uint32_t input[16];
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#ifndef CHACHA20_POLY1305_H
|
||||
#define CHACHA20_POLY1305_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHACHA20_BLOCKSIZE 64
|
||||
#define CHACHA20_KEYLEN 32
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ int ssh_config_get_yesno(char **str, int notfound);
|
||||
* be stored or NULL if we do not care about the result.
|
||||
* @param[in] ignore_port Set to true if we should not attempt to parse
|
||||
* port number.
|
||||
* @param[in] strict Set to true to validate hostname against RFC1035
|
||||
* (for resolving to a real host).
|
||||
* Set to false to only reject shell metacharacters
|
||||
* (allowing config aliases with non-RFC1035 chars
|
||||
* like underscores, resolved later via Hostname).
|
||||
*
|
||||
* @returns SSH_OK if the provided string is in format of SSH URI,
|
||||
* SSH_ERROR on failure
|
||||
@@ -62,7 +67,8 @@ int ssh_config_parse_uri(const char *tok,
|
||||
char **username,
|
||||
char **hostname,
|
||||
char **port,
|
||||
bool ignore_port);
|
||||
bool ignore_port,
|
||||
bool strict);
|
||||
|
||||
/**
|
||||
* @brief: Parse the ProxyJump configuration line and if parsing,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#ifndef SSH_KNOWNHOSTS_H_
|
||||
#define SSH_KNOWNHOSTS_H_
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -67,38 +67,60 @@ class Channel;
|
||||
*/
|
||||
#ifndef SSH_NO_CPP_EXCEPTIONS
|
||||
|
||||
/** @brief This class describes a SSH Exception object. This object can be thrown
|
||||
* by several SSH functions that interact with the network, and may fail because of
|
||||
* socket, protocol or memory errors.
|
||||
/** @brief This class describes a SSH Exception object. This object can be
|
||||
* thrown by several SSH functions that interact with the network, and
|
||||
* may fail because of socket, protocol or memory errors.
|
||||
*/
|
||||
class SshException{
|
||||
public:
|
||||
SshException(ssh_session csession){
|
||||
code=ssh_get_error_code(csession);
|
||||
description=std::string(ssh_get_error(csession));
|
||||
}
|
||||
SshException(const SshException &e){
|
||||
code=e.code;
|
||||
description=e.description;
|
||||
}
|
||||
/** @brief returns the Error code
|
||||
* @returns SSH_FATAL Fatal error happened (not recoverable)
|
||||
* @returns SSH_REQUEST_DENIED Request was denied by remote host
|
||||
* @see ssh_get_error_code
|
||||
*/
|
||||
int getCode(){
|
||||
return code;
|
||||
}
|
||||
/** @brief returns the error message of the last exception
|
||||
* @returns pointer to a c string containing the description of error
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
std::string getError(){
|
||||
return description;
|
||||
}
|
||||
private:
|
||||
int code;
|
||||
std::string description;
|
||||
class SshException {
|
||||
public:
|
||||
/** @brief Construct an exception from a libssh session error state.
|
||||
* Captures the current error code and error string associated with
|
||||
* the given session.
|
||||
*
|
||||
* @param[in] csession libssh session handle used to query error details.
|
||||
*
|
||||
* @see ssh_get_error_code
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
SshException(ssh_session csession)
|
||||
{
|
||||
code = ssh_get_error_code(csession);
|
||||
description = std::string(ssh_get_error(csession));
|
||||
}
|
||||
/** @brief Copy-construct an exception.
|
||||
*
|
||||
* @param[in] e Source exception.
|
||||
*/
|
||||
SshException(const SshException &e)
|
||||
{
|
||||
code = e.code;
|
||||
description = e.description;
|
||||
}
|
||||
/** @brief returns the Error code
|
||||
*
|
||||
* @returns `SSH_FATAL` Fatal error happened (not recoverable)
|
||||
* @returns `SSH_REQUEST_DENIED` Request was denied by remote host
|
||||
*
|
||||
* @see ssh_get_error_code
|
||||
*/
|
||||
int getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
/** @brief returns the error message of the last exception
|
||||
*
|
||||
* @returns pointer to a c string containing the description of error
|
||||
*
|
||||
* @see ssh_get_error
|
||||
*/
|
||||
std::string getError()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
private:
|
||||
int code;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
/** @internal
|
||||
@@ -134,9 +156,12 @@ public:
|
||||
c_session=NULL;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option cstring containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option cstring containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, const char *option){
|
||||
@@ -144,9 +169,12 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option long integer containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option long integer containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, long int option){
|
||||
@@ -154,9 +182,12 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief sets an SSH session options
|
||||
* @param type Type of option
|
||||
* @param option void pointer containing the value of option
|
||||
*
|
||||
* @param[in] type Type of option
|
||||
* @param[in] option void pointer containing the value of option
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_options_set
|
||||
*/
|
||||
void_throwable setOption(enum ssh_options_e type, void *option){
|
||||
@@ -164,7 +195,9 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief connects to the remote host
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @see ssh_connect
|
||||
*/
|
||||
void_throwable connect(){
|
||||
@@ -173,8 +206,11 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Authenticates automatically using public key
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_autopubkey
|
||||
*/
|
||||
int userauthPublickeyAuto(void){
|
||||
@@ -184,9 +220,13 @@ public:
|
||||
}
|
||||
/** @brief Authenticates using the "none" method. Prefer using autopubkey if
|
||||
* possible.
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_none
|
||||
*
|
||||
* @see Session::userauthAutoPubkey
|
||||
*/
|
||||
int userauthNone(){
|
||||
@@ -198,16 +238,16 @@ public:
|
||||
/**
|
||||
* @brief Authenticate through the "keyboard-interactive" method.
|
||||
*
|
||||
* @param[in] username The username to authenticate. You can specify NULL if
|
||||
* ssh_option_set_username() has been used. You cannot
|
||||
* try two different logins in a row.
|
||||
*
|
||||
* @param[in] username The username to authenticate. You can specify NULL
|
||||
* If ssh_option_set_username()has been used. You cannot
|
||||
* try two different logins in a row.
|
||||
* @param[in] submethods Undocumented. Set it to NULL.
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED,
|
||||
* SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`,
|
||||
* `SSH_AUTH_DENIED`, `SSH_AUTH_ERROR`, `SSH_AUTH_INFO`,
|
||||
* `SSH_AUTH_AGAIN`
|
||||
*
|
||||
* @see ssh_userauth_kbdint
|
||||
*/
|
||||
@@ -218,6 +258,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief Get the number of prompts (questions) the server has given.
|
||||
*
|
||||
* @returns The number of prompts.
|
||||
* @see ssh_userauth_kbdint_getnprompts
|
||||
*/
|
||||
@@ -228,17 +269,16 @@ public:
|
||||
/**
|
||||
* @brief Set the answer for a question from a message block.
|
||||
*
|
||||
* @param[in] index The index number of the prompt.
|
||||
* @param[in] answer The answer to give to the server. The answer MUST be
|
||||
* encoded UTF-8. It is up to the server how to interpret
|
||||
* the value and validate it. However, if you read the
|
||||
* answer in some other encoding, you MUST convert it to
|
||||
* UTF-8.
|
||||
* @param[in] index The index number of the prompt.
|
||||
* @param[in] answer The answer to give to the server. The answer MUST be
|
||||
* encoded UTF-8.It is up to the server how to interpret
|
||||
* the value and validate it. However, if you read the
|
||||
* answer in some other encoding, you MUST convert it to
|
||||
* UTF-8.
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns 0 on success, < 0 on error
|
||||
*
|
||||
* @see ssh_userauth_kbdint_setanswer
|
||||
*/
|
||||
int userauthKbdintSetAnswer(unsigned int index, const char *answer)
|
||||
@@ -248,12 +288,13 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @brief Authenticates using the password method.
|
||||
*
|
||||
* @param[in] password password to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_password
|
||||
*/
|
||||
int userauthPassword(const char *password){
|
||||
@@ -262,10 +303,14 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief Try to authenticate using the publickey method.
|
||||
*
|
||||
* @param[in] pubkey public key to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS if the pubkey is accepted,
|
||||
* @returns SSH_AUTH_DENIED if the pubkey is denied
|
||||
*
|
||||
* @returns `SSH_AUTH_SUCCESS` if the pubkey is accepted,
|
||||
* @returns `SSH_AUTH_DENIED` if the pubkey is denied
|
||||
*
|
||||
* @see ssh_userauth_try_pubkey
|
||||
*/
|
||||
int userauthTryPublickey(ssh_key pubkey){
|
||||
@@ -274,9 +319,12 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief Authenticates using the publickey method.
|
||||
*
|
||||
* @param[in] privkey private key to use for authentication
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED
|
||||
* @returns `SSH_AUTH_SUCCESS`, `SSH_AUTH_PARTIAL`, `SSH_AUTH_DENIED`
|
||||
*
|
||||
* @see ssh_userauth_pubkey
|
||||
*/
|
||||
int userauthPublickey(ssh_key privkey){
|
||||
@@ -286,7 +334,9 @@ public:
|
||||
}
|
||||
|
||||
/** @brief Returns the available authentication methods from the server
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @returns Bitfield of available methods.
|
||||
* @see ssh_userauth_list
|
||||
*/
|
||||
@@ -302,8 +352,9 @@ public:
|
||||
ssh_disconnect(c_session);
|
||||
}
|
||||
/** @brief Returns the disconnect message from the server, if any
|
||||
* @returns pointer to the message, or NULL. Do not attempt to free
|
||||
* the pointer.
|
||||
*
|
||||
* @returns pointer to the message, or NULL. Do not attempt to free the
|
||||
* pointer.
|
||||
*/
|
||||
const char *getDisconnectMessage(){
|
||||
const char *msg=ssh_get_disconnect_message(c_session);
|
||||
@@ -312,25 +363,30 @@ public:
|
||||
/** @internal
|
||||
* @brief gets error message
|
||||
*/
|
||||
const char *getError(){
|
||||
return ssh_get_error(c_session);
|
||||
const char *getError()
|
||||
{
|
||||
return ssh_get_error(c_session);
|
||||
}
|
||||
/** @internal
|
||||
* @brief returns error code
|
||||
*/
|
||||
int getErrorCode(){
|
||||
return ssh_get_error_code(c_session);
|
||||
int getErrorCode()
|
||||
{
|
||||
return ssh_get_error_code(c_session);
|
||||
}
|
||||
/** @brief returns the file descriptor used for the communication
|
||||
*
|
||||
* @returns the file descriptor
|
||||
*
|
||||
* @warning if a proxycommand is used, this function will only return
|
||||
* one of the two file descriptors being used
|
||||
* one of the two file descriptors being used.
|
||||
* @see ssh_get_fd
|
||||
*/
|
||||
socket_t getSocket(){
|
||||
return ssh_get_fd(c_session);
|
||||
}
|
||||
/** @brief gets the Issue banner from the ssh server
|
||||
*
|
||||
* @returns the issue banner. This is generally a MOTD from server
|
||||
* @see ssh_get_issue_banner
|
||||
*/
|
||||
@@ -344,6 +400,7 @@ public:
|
||||
return ret;
|
||||
}
|
||||
/** @brief returns the OpenSSH version (server) if possible
|
||||
*
|
||||
* @returns openssh version code
|
||||
* @see ssh_get_openssh_version
|
||||
*/
|
||||
@@ -351,6 +408,7 @@ public:
|
||||
return ssh_get_openssh_version(c_session);
|
||||
}
|
||||
/** @brief returns the version of the SSH protocol being used
|
||||
*
|
||||
* @returns the SSH protocol version
|
||||
* @see ssh_get_version
|
||||
*/
|
||||
@@ -358,9 +416,10 @@ public:
|
||||
return ssh_get_version(c_session);
|
||||
}
|
||||
/** @brief verifies that the server is known
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @returns Integer value depending on the knowledge of the
|
||||
* server key
|
||||
*
|
||||
* @returns Integer value depending on the knowledge of the server key
|
||||
* @see ssh_session_update_known_hosts
|
||||
*/
|
||||
int isServerKnown(){
|
||||
@@ -377,6 +436,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief copies options from a session to another
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_options_copy
|
||||
*/
|
||||
@@ -385,8 +445,11 @@ public:
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief parses a configuration file for options
|
||||
*
|
||||
* @throws SshException on error
|
||||
*
|
||||
* @param[in] file configuration file name
|
||||
*
|
||||
* @see ssh_options_parse_config
|
||||
*/
|
||||
void_throwable optionsParseConfig(const char *file){
|
||||
@@ -399,8 +462,8 @@ public:
|
||||
void silentDisconnect(){
|
||||
ssh_silent_disconnect(c_session);
|
||||
}
|
||||
/** @brief Writes the known host file with current
|
||||
* host key
|
||||
/** @brief Writes the known host file with current host key
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_write_knownhost
|
||||
*/
|
||||
@@ -411,11 +474,15 @@ public:
|
||||
}
|
||||
|
||||
/** @brief accept an incoming forward connection
|
||||
*
|
||||
* @param[in] timeout_ms timeout for waiting, in ms
|
||||
*
|
||||
* @returns new Channel pointer on the forward connection
|
||||
* @returns NULL in case of error
|
||||
*
|
||||
* @warning you have to delete this pointer after use
|
||||
* @see ssh_channel_forward_accept
|
||||
*
|
||||
* @see Session::listenForward
|
||||
*/
|
||||
inline Channel *acceptForward(int timeout_ms);
|
||||
@@ -439,6 +506,9 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
/** @internal
|
||||
* @brief Underlying libssh session handle.
|
||||
*/
|
||||
ssh_session c_session;
|
||||
|
||||
private:
|
||||
@@ -447,8 +517,7 @@ private:
|
||||
Session& operator=(const Session &);
|
||||
};
|
||||
|
||||
/** @brief the ssh::Channel class describes the state of an SSH
|
||||
* channel.
|
||||
/** @brief the ssh::Channel class describes the state of an SSH channel.
|
||||
* @see ssh_channel
|
||||
*/
|
||||
class Channel {
|
||||
@@ -464,11 +533,15 @@ public:
|
||||
}
|
||||
|
||||
/** @brief accept an incoming X11 connection
|
||||
*
|
||||
* @param[in] timeout_ms timeout for waiting, in ms
|
||||
*
|
||||
* @returns new Channel pointer on the X11 connection
|
||||
* @returns NULL in case of error
|
||||
*
|
||||
* @warning you have to delete this pointer after use
|
||||
* @see ssh_channel_accept_x11
|
||||
*
|
||||
* @see Channel::requestX11
|
||||
*/
|
||||
Channel *acceptX11(int timeout_ms){
|
||||
@@ -478,8 +551,10 @@ public:
|
||||
return newchan;
|
||||
}
|
||||
/** @brief change the size of a pseudoterminal
|
||||
*
|
||||
* @param[in] cols number of columns
|
||||
* @param[in] rows number of rows
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_channel_change_pty_size
|
||||
*/
|
||||
@@ -490,6 +565,7 @@ public:
|
||||
}
|
||||
|
||||
/** @brief closes a channel
|
||||
*
|
||||
* @throws SshException on error
|
||||
* @see ssh_channel_close
|
||||
*/
|
||||
@@ -536,6 +612,21 @@ public:
|
||||
bool isOpen(){
|
||||
return ssh_channel_is_open(channel) != 0;
|
||||
}
|
||||
/** @brief Open a TCP forward channel.
|
||||
*
|
||||
* @param[in] remotehost Remote host to connect to.
|
||||
* @param[in] remoteport Remote port to connect to.
|
||||
* @param[in] sourcehost Source address to report (can be NULL depending on
|
||||
* server policy).
|
||||
* @param[in] localport Local port to report (0 lets the server pick if
|
||||
* applicable).
|
||||
*
|
||||
* @returns `SSH_OK` (0) on success.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_open_forward
|
||||
*/
|
||||
int openForward(const char *remotehost, int remoteport,
|
||||
const char *sourcehost, int localport=0){
|
||||
int err=ssh_channel_open_forward(channel,remotehost,remoteport,
|
||||
@@ -549,20 +640,55 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
int poll(bool is_stderr=false){
|
||||
int err=ssh_channel_poll(channel,is_stderr);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
/** @brief Poll the channel for available data.
|
||||
*
|
||||
* @param[in] is_stderr If true, poll stderr stream; otherwise stdout.
|
||||
*
|
||||
* @returns Number of bytes available to read (>= 0).
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_poll
|
||||
*/
|
||||
int poll(bool is_stderr = false)
|
||||
{
|
||||
int err = ssh_channel_poll(channel, is_stderr);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
int read(void *dest, size_t count){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
if(count > 0x7fffffff)
|
||||
count = 0x7fffffff;
|
||||
err=ssh_channel_read_timeout(channel,dest,count,false,-1);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
/** @brief Read data from the channel (blocking).
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return of 0 indicates EOF/no data.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count)
|
||||
{
|
||||
int err;
|
||||
if (count > 0x7fffffff)
|
||||
count = 0x7fffffff;
|
||||
err = ssh_channel_read_timeout(channel, dest, count, false, -1);
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel with a timeout.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] timeout Timeout in milliseconds. A negative value means
|
||||
* infinite timeout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return value of 0 indicates EOF.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count, int timeout){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -572,6 +698,22 @@ public:
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel with optional stderr selection and
|
||||
* timeout.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] is_stderr If true, read from the stderr stream; otherwise
|
||||
* read from stdout.
|
||||
* @param[in] timeout Timeout in milliseconds. A negative value means
|
||||
* infinite timeout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return value of 0 indicates EOF.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_timeout
|
||||
*/
|
||||
int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -581,6 +723,19 @@ public:
|
||||
ssh_throw(err);
|
||||
return err;
|
||||
}
|
||||
/** @brief Read data from the channel without blocking.
|
||||
*
|
||||
* @param[out] dest Destination buffer.
|
||||
* @param[in] count Maximum number of bytes to read.
|
||||
* @param[in] is_stderr If true, read from the stderr stream; otherwise read
|
||||
* from stdout.
|
||||
*
|
||||
* @returns Number of bytes read (>= 0). A return of 0 may indicate no data.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*
|
||||
* @throws SshException on error (exception-enabled builds).
|
||||
* @see ssh_channel_read_nonblocking
|
||||
*/
|
||||
int readNonblocking(void *dest, size_t count, bool is_stderr=false){
|
||||
int err;
|
||||
/* handle int overflow */
|
||||
@@ -629,6 +784,18 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Request X11 forwarding for this channel.
|
||||
*
|
||||
* @param[in] single_connection If true, allow only a single X11 connection
|
||||
* for this channel; further X11 connections are
|
||||
* refused after the first is accepted.
|
||||
* @param[in] protocol X11 authentication protocol.
|
||||
* @param[in] cookie X11 authentication cookie.
|
||||
* @param[in] screen_number X11 screen number.
|
||||
*
|
||||
* @returns `SSH_OK` on success.
|
||||
* @returns `SSH_ERROR` on error (no-exception builds).
|
||||
*/
|
||||
int requestX11(bool single_connection,
|
||||
const char *protocol, const char *cookie, int screen_number){
|
||||
int err=ssh_channel_request_x11(channel,single_connection,
|
||||
@@ -641,11 +808,16 @@ public:
|
||||
ssh_throw(err);
|
||||
return_throwable;
|
||||
}
|
||||
/** @brief Writes on a channel
|
||||
* @param data data to write.
|
||||
* @param len number of bytes to write.
|
||||
* @param is_stderr write should be done on the stderr channel (server only)
|
||||
/**
|
||||
* @brief Writes on a channel
|
||||
*
|
||||
* @param[in] data data to write.
|
||||
* @param[in] len number of bytes to write.
|
||||
* @param[in] is_stderr write should be done on the stderr channel (server
|
||||
* only)
|
||||
*
|
||||
* @returns number of bytes written
|
||||
*
|
||||
* @throws SshException in case of error
|
||||
* @see ssh_channel_write
|
||||
* @see ssh_channel_write_stderr
|
||||
@@ -670,7 +842,13 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
/** @internal
|
||||
* @brief Parent session owning this channel.
|
||||
*/
|
||||
Session *session;
|
||||
/** @internal
|
||||
* @brief Underlying libssh channel handle.
|
||||
*/
|
||||
ssh_channel channel;
|
||||
|
||||
private:
|
||||
@@ -683,7 +861,6 @@ private:
|
||||
Channel &operator=(const Channel &);
|
||||
};
|
||||
|
||||
|
||||
inline Channel *Session::acceptForward(int timeout_ms){
|
||||
ssh_channel forward =
|
||||
ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#ifndef POLY1305_H
|
||||
#define POLY1305_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libssh/chacha20-poly1305-common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#ifndef SC25519_H
|
||||
#define SC25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define sc25519 crypto_sign_ed25519_ref_sc25519
|
||||
#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519
|
||||
#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes
|
||||
|
||||
@@ -21,6 +21,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
|
||||
|
||||
@@ -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,
|
||||
@@ -102,12 +107,31 @@ LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind,
|
||||
LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind,
|
||||
const char *filename);
|
||||
|
||||
LIBSSH_API int ssh_bind_config_parse_string(ssh_bind bind, const char *input);
|
||||
|
||||
/**
|
||||
* @brief Start listening to the socket.
|
||||
*
|
||||
* @param ssh_bind_o The ssh server bind to use.
|
||||
*
|
||||
* @return 0 on success, < 0 on error.
|
||||
*
|
||||
* @warning This function implicitly calls ssh_bind_options_parse_config()
|
||||
* to process system-wide and user configuration files unless
|
||||
* configuration processing was already performed explicitly
|
||||
* by the caller.\n
|
||||
* As a result, any options previously set (e.g., manually via
|
||||
* ssh_bind_options_set() or ssh_bind_config_parse_string()) may be
|
||||
* overridden by values from the configuration files.\n
|
||||
* To guarantee that explicitly set options take precedence,
|
||||
* callers of this function should either:
|
||||
* - call ssh_bind_options_parse_config() themselves before
|
||||
* setting options, or
|
||||
* - disable automatic config processing via
|
||||
* SSH_BIND_OPTIONS_PROCESS_CONFIG (set to false).
|
||||
*
|
||||
* @see ssh_bind_options_parse_config()
|
||||
* @see ssh_bind_options_set()
|
||||
*/
|
||||
LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o);
|
||||
|
||||
|
||||
@@ -286,6 +286,9 @@ struct ssh_session_struct {
|
||||
int control_master;
|
||||
char *control_path;
|
||||
int address_family;
|
||||
char *originalhost; /* user-supplied host for config matching */
|
||||
bool config_hostname_only; /* config hostname path: update host only,
|
||||
not originalhost */
|
||||
} opts;
|
||||
|
||||
/* server options */
|
||||
|
||||
@@ -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 {
|
||||
@@ -177,28 +244,113 @@ struct sftp_status_message_struct {
|
||||
char *langmsg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SFTP file attributes structure.
|
||||
*
|
||||
* This type represents file attributes. It is used both for
|
||||
* sending and receiving file attributes from the server.
|
||||
*
|
||||
* `flags` determines which of the struct fields are present.
|
||||
*
|
||||
* @see sftp_attributes_free()
|
||||
*/
|
||||
struct sftp_attributes_struct {
|
||||
/** File name */
|
||||
char *name;
|
||||
char *longname; /* ls -l output on openssh, not reliable else */
|
||||
|
||||
/** Extended name i.e output of `ls -l` (requires SFTP v3 with OpenSSH) */
|
||||
char *longname;
|
||||
|
||||
/** Determines which of the struct fields are present */
|
||||
uint32_t flags;
|
||||
|
||||
/** File type */
|
||||
uint8_t type;
|
||||
|
||||
/** File size (requires flag `SSH_FILEXFER_ATTR_SIZE`) */
|
||||
uint64_t size;
|
||||
|
||||
/** User ID (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_UIDGID`) */
|
||||
uint32_t uid;
|
||||
|
||||
/** Group ID (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_UIDGID`) */
|
||||
uint32_t gid;
|
||||
char *owner; /* set if openssh and version 4 */
|
||||
char *group; /* set if openssh and version 4 */
|
||||
|
||||
/**
|
||||
* File owner
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_OWNERGROUP`
|
||||
* or SFTP v3 with OpenSSH)
|
||||
*/
|
||||
char *owner;
|
||||
|
||||
/**
|
||||
* File group
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_OWNERGROUP`
|
||||
* or SFTP v3 with OpenSSH)
|
||||
*/
|
||||
char *group;
|
||||
|
||||
/** File permissions (requires flag `SSH_FILEXFER_ATTR_PERMISSIONS`) */
|
||||
uint32_t permissions;
|
||||
|
||||
/**
|
||||
* Access time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_ACCESSTIME`)
|
||||
*/
|
||||
uint64_t atime64;
|
||||
|
||||
/**
|
||||
* Access time
|
||||
* (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_ACMODTIME`)
|
||||
*/
|
||||
uint32_t atime;
|
||||
|
||||
/**
|
||||
* Access time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t atime_nseconds;
|
||||
|
||||
/**
|
||||
* Creation time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_CREATETIME`)
|
||||
*/
|
||||
uint64_t createtime;
|
||||
|
||||
/**
|
||||
* Creation time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t createtime_nseconds;
|
||||
|
||||
/**
|
||||
* Modification time
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_MODIFYTIME`)
|
||||
*/
|
||||
uint64_t mtime64;
|
||||
|
||||
/**
|
||||
* Modification time
|
||||
* (requires SFTP v3 with flag `SSH_FILEXFER_ATTR_ACMODTIME`)
|
||||
*/
|
||||
uint32_t mtime;
|
||||
|
||||
/**
|
||||
* Modification time nanoseconds
|
||||
* (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_SUBSECOND_TIMES`)
|
||||
*/
|
||||
uint32_t mtime_nseconds;
|
||||
|
||||
/** ACL data (requires SFTP v4 with flag `SSH_FILEXFER_ATTR_ACL`) */
|
||||
ssh_string acl;
|
||||
|
||||
/** Unused */
|
||||
uint32_t extended_count;
|
||||
|
||||
/** Unused */
|
||||
ssh_string extended_type;
|
||||
|
||||
/** Unused */
|
||||
ssh_string extended_data;
|
||||
};
|
||||
|
||||
@@ -206,27 +358,55 @@ struct sftp_attributes_struct {
|
||||
* @brief SFTP statvfs structure.
|
||||
*/
|
||||
struct sftp_statvfs_struct {
|
||||
uint64_t f_bsize; /** file system block size */
|
||||
uint64_t f_frsize; /** fundamental fs block size */
|
||||
uint64_t f_blocks; /** number of blocks (unit f_frsize) */
|
||||
uint64_t f_bfree; /** free blocks in file system */
|
||||
uint64_t f_bavail; /** free blocks for non-root */
|
||||
uint64_t f_files; /** total file inodes */
|
||||
uint64_t f_ffree; /** free file inodes */
|
||||
uint64_t f_favail; /** free file inodes for to non-root */
|
||||
uint64_t f_fsid; /** file system id */
|
||||
uint64_t f_flag; /** bit mask of f_flag values */
|
||||
uint64_t f_namemax; /** maximum filename length */
|
||||
/** file system block size */
|
||||
uint64_t f_bsize;
|
||||
|
||||
/** fundamental fs block size */
|
||||
uint64_t f_frsize;
|
||||
|
||||
/** number of blocks (unit f_frsize) */
|
||||
uint64_t f_blocks;
|
||||
|
||||
/** free blocks in file system */
|
||||
uint64_t f_bfree;
|
||||
|
||||
/** free blocks for non-root */
|
||||
uint64_t f_bavail;
|
||||
|
||||
/** total file inodes */
|
||||
uint64_t f_files;
|
||||
|
||||
/** free file inodes */
|
||||
uint64_t f_ffree;
|
||||
|
||||
/** free file inodes for non-root */
|
||||
uint64_t f_favail;
|
||||
|
||||
/** file system id */
|
||||
uint64_t f_fsid;
|
||||
|
||||
/** bit mask of f_flag values */
|
||||
uint64_t f_flag;
|
||||
|
||||
/** maximum filename length */
|
||||
uint64_t f_namemax;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief SFTP limits structure.
|
||||
*/
|
||||
struct sftp_limits_struct {
|
||||
uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */
|
||||
uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */
|
||||
uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */
|
||||
uint64_t max_open_handles; /** maximum number of active handles allowed by server */
|
||||
/** maximum number of bytes in a single sftp packet */
|
||||
uint64_t max_packet_length;
|
||||
|
||||
/** maximum length in a SSH_FXP_READ packet */
|
||||
uint64_t max_read_length;
|
||||
|
||||
/** maximum length in a SSH_FXP_WRITE packet */
|
||||
uint64_t max_write_length;
|
||||
|
||||
/** maximum number of active handles allowed by server */
|
||||
uint64_t max_open_handles;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -279,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.
|
||||
*
|
||||
@@ -1469,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
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" {
|
||||
|
||||
88
src/agent.c
88
src/agent.c
@@ -635,3 +635,91 @@ ssh_string ssh_agent_sign_data(ssh_session session,
|
||||
|
||||
return sig_blob;
|
||||
}
|
||||
|
||||
int ssh_agent_remove_identity(ssh_session session,
|
||||
const ssh_key key)
|
||||
{
|
||||
ssh_buffer request = NULL;
|
||||
ssh_buffer reply = NULL;
|
||||
ssh_string key_blob = NULL;
|
||||
uint8_t type = 0;
|
||||
int rc = SSH_ERROR;
|
||||
|
||||
if (session == NULL || key == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (session->agent == NULL) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"No agent connection available");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/* Connect to the agent if not already connected */
|
||||
if (!ssh_socket_is_open(session->agent->sock)) {
|
||||
if (agent_connect(session) < 0) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Could not connect to SSH agent");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
request = ssh_buffer_new();
|
||||
if (request == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_add_u8(request, SSH2_AGENTC_REMOVE_IDENTITY) < 0) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_pki_export_pubkey_blob(key, &key_blob) < 0) {
|
||||
ssh_set_error(session, SSH_FATAL, "Failed to export public key blob");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_add_ssh_string(request, key_blob) < 0) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
reply = ssh_buffer_new();
|
||||
if (reply == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (agent_talk(session, request, reply) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ssh_buffer_get_u8(reply, &type) != sizeof(uint8_t)) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Failed to read agent reply type");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (agent_failed(type)) {
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure removing identity");
|
||||
goto fail;
|
||||
} else if (type != SSH_AGENT_SUCCESS) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"Agent refused to remove identity: reply type %u",
|
||||
type);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rc = SSH_OK;
|
||||
|
||||
fail:
|
||||
SSH_STRING_FREE(key_blob);
|
||||
SSH_BUFFER_FREE(request);
|
||||
SSH_BUFFER_FREE(reply);
|
||||
return rc;
|
||||
}
|
||||
|
||||
24
src/auth.c
24
src/auth.c
@@ -1397,7 +1397,7 @@ int ssh_userauth_publickey_auto(ssh_session session,
|
||||
return SSH_AUTH_ERROR;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Starting authentication as a user %s",
|
||||
username ? username : session->opts.username);
|
||||
|
||||
@@ -1835,6 +1835,14 @@ int ssh_userauth_agent_pubkey(ssh_session session,
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Allocates memory for keyboard interactive auth structure.
|
||||
*
|
||||
* @return A newly allocated ssh_kbdint structure `kbd` on success, NULL on failure.
|
||||
* The caller is responsible for freeing allocated memory.
|
||||
*/
|
||||
ssh_kbdint ssh_kbdint_new(void)
|
||||
{
|
||||
ssh_kbdint kbd;
|
||||
@@ -1847,7 +1855,11 @@ ssh_kbdint ssh_kbdint_new(void)
|
||||
return kbd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory for keyboard interactive auth structure.
|
||||
*
|
||||
* @param[in] kbd The keyboard interactive structure to free.
|
||||
*/
|
||||
void ssh_kbdint_free(ssh_kbdint kbd)
|
||||
{
|
||||
size_t i, n;
|
||||
@@ -1885,6 +1897,14 @@ void ssh_kbdint_free(ssh_kbdint kbd)
|
||||
SAFE_FREE(kbd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean a keyboard interactive auth structure.
|
||||
*
|
||||
* Clears structure's fields and resets nanswers and nprompts to 0, allowing
|
||||
* reuse.
|
||||
*
|
||||
* @param[in] kbd The keyboard interactive struct to clean
|
||||
*/
|
||||
void ssh_kbdint_clean(ssh_kbdint kbd)
|
||||
{
|
||||
size_t i, n;
|
||||
|
||||
@@ -327,7 +327,7 @@ static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int reve
|
||||
/** @internal
|
||||
* @brief returns the current poll handle, or creates it
|
||||
* @param sshbind the ssh_bind object
|
||||
* @returns a ssh_poll handle suitable for operation
|
||||
* @return a ssh_poll handle suitable for operation
|
||||
*/
|
||||
ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind)
|
||||
{
|
||||
|
||||
@@ -676,12 +676,21 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @brief Parse configuration string and set the options to the given bind session
|
||||
/**
|
||||
* @brief Parse configuration string and set the options to the given bind
|
||||
* session
|
||||
*
|
||||
* @params[in] bind The ssh bind session
|
||||
* @params[in] input Null terminated string containing the configuration
|
||||
* @param[in] bind The ssh bind session
|
||||
* @param[in] input Null terminated string containing the configuration
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* @warning Options set via this function may be overridden if a configuration
|
||||
* file is parsed afterwards (e.g., by an implicit call to
|
||||
* ssh_bind_options_parse_config() inside ssh_bind_listen(), or by a
|
||||
* manual call to the same function) and contains the same options.\n
|
||||
* It is the caller’s responsibility to ensure the correct order of
|
||||
* API calls if explicit options must take precedence.
|
||||
*
|
||||
* @return SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
*/
|
||||
int ssh_bind_config_parse_string(ssh_bind bind, const char *input)
|
||||
@@ -713,21 +722,29 @@ int ssh_bind_config_parse_string(ssh_bind bind, const char *input)
|
||||
}
|
||||
if (c == NULL) {
|
||||
/* should not happen, would mean a string without trailing '\0' */
|
||||
SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string");
|
||||
ssh_set_error(bind,
|
||||
SSH_FATAL,
|
||||
"No trailing '\\0' in config string");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
line_len = c - line_start;
|
||||
if (line_len > MAX_LINE_SIZE - 1) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"Line %u too long: %zu characters",
|
||||
line_num,
|
||||
line_len);
|
||||
ssh_set_error(bind,
|
||||
SSH_FATAL,
|
||||
"Line %u too long: %zu characters",
|
||||
line_num,
|
||||
line_len);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
memcpy(line, line_start, line_len);
|
||||
line[line_len] = '\0';
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
|
||||
rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0);
|
||||
rv = ssh_bind_config_parse_line(bind,
|
||||
line,
|
||||
line_num,
|
||||
&parser_flags,
|
||||
seen,
|
||||
0);
|
||||
if (rv < 0) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
39
src/buffer.c
39
src/buffer.c
@@ -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
|
||||
|
||||
@@ -921,18 +926,10 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer,
|
||||
va_arg(ap, void *);
|
||||
count++; /* increase argument count */
|
||||
break;
|
||||
case 'F':
|
||||
case 'B':
|
||||
b = va_arg(ap, bignum);
|
||||
if (*p == 'F') {
|
||||
/* For padded bignum, we know the exact length */
|
||||
len = va_arg(ap, size_t);
|
||||
count++; /* increase argument count */
|
||||
needed_size += sizeof(uint32_t) + len;
|
||||
} else {
|
||||
/* The bignum bytes + 1 for possible padding */
|
||||
needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1;
|
||||
}
|
||||
/* The bignum bytes + 1 for possible padding */
|
||||
needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1;
|
||||
break;
|
||||
case 't':
|
||||
cstring = va_arg(ap, char *);
|
||||
@@ -972,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.
|
||||
@@ -1062,16 +1062,9 @@ ssh_buffer_pack_va(struct ssh_buffer_struct *buffer,
|
||||
rc = ssh_buffer_add_data(buffer, o.data, (uint32_t)len);
|
||||
o.data = NULL;
|
||||
break;
|
||||
case 'F':
|
||||
case 'B':
|
||||
b = va_arg(ap, bignum);
|
||||
if (*p == 'F') {
|
||||
len = va_arg(ap, size_t);
|
||||
count++; /* increase argument count */
|
||||
o.string = ssh_make_padded_bignum_string(b, len);
|
||||
} else {
|
||||
o.string = ssh_make_bignum_string(b);
|
||||
}
|
||||
o.string = ssh_make_bignum_string(b);
|
||||
if(o.string == NULL){
|
||||
rc = SSH_ERROR;
|
||||
break;
|
||||
@@ -1127,8 +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)
|
||||
* 'F': bignum, size_t (bignum, padded to fixed length,
|
||||
* 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
|
||||
@@ -1165,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.
|
||||
@@ -1428,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
|
||||
|
||||
@@ -3517,7 +3517,7 @@ int ssh_channel_get_exit_state(ssh_channel channel,
|
||||
*pexit_signal = NULL;
|
||||
if (channel->exit.signal != NULL) {
|
||||
*pexit_signal = strdup(channel->exit.signal);
|
||||
if (pexit_signal == NULL) {
|
||||
if (*pexit_signal == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
@@ -582,9 +582,8 @@ int ssh_connect(ssh_session session)
|
||||
session->client = 1;
|
||||
|
||||
if (session->opts.fd == SSH_INVALID_SOCKET &&
|
||||
session->opts.host == NULL &&
|
||||
session->opts.ProxyCommand == NULL)
|
||||
{
|
||||
session->opts.originalhost == NULL &&
|
||||
session->opts.ProxyCommand == NULL) {
|
||||
ssh_set_error(session, SSH_FATAL, "Hostname required");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
65
src/config.c
65
src/config.c
@@ -258,7 +258,9 @@ local_parse_file(ssh_session session,
|
||||
|
||||
f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
|
||||
if (f == NULL) {
|
||||
/* The underlying function logs the reasons */
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
"Failed to open included configuration file %s",
|
||||
filename);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -542,6 +544,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
&jump_host->username,
|
||||
&jump_host->hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
@@ -564,7 +567,12 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
}
|
||||
} else if (parse_entry) {
|
||||
/* We actually care only about the first item */
|
||||
rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false);
|
||||
rv = ssh_config_parse_uri(cp,
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -580,7 +588,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
}
|
||||
} else {
|
||||
/* The rest is just sanity-checked to avoid failures later */
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -1038,20 +1046,20 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
break;
|
||||
|
||||
case MATCH_ORIGINALHOST:
|
||||
/* Skip one argument */
|
||||
/* Here we match only one argument */
|
||||
p = ssh_config_get_str_tok(&s, NULL);
|
||||
if (p == NULL || p[0] == '\0') {
|
||||
SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
|
||||
"'%s' requires argument", count, p2);
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"line %d: ERROR - Match originalhost keyword "
|
||||
"requires argument",
|
||||
count);
|
||||
SAFE_FREE(x);
|
||||
return -1;
|
||||
}
|
||||
result &=
|
||||
ssh_config_match(session->opts.originalhost, p, negate);
|
||||
args++;
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"line %d: Unsupported Match keyword '%s', ignoring",
|
||||
count,
|
||||
p2);
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case MATCH_HOST:
|
||||
@@ -1064,7 +1072,11 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
SAFE_FREE(x);
|
||||
return -1;
|
||||
}
|
||||
result &= ssh_config_match(session->opts.host, p, negate);
|
||||
result &= ssh_config_match(session->opts.host
|
||||
? session->opts.host
|
||||
: session->opts.originalhost,
|
||||
p,
|
||||
negate);
|
||||
args++;
|
||||
break;
|
||||
|
||||
@@ -1152,7 +1164,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
int ok = 0, result = -1;
|
||||
|
||||
*parsing = 0;
|
||||
lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
|
||||
lowerhost = (session->opts.originalhost)
|
||||
? ssh_lowercase(session->opts.originalhost)
|
||||
: NULL;
|
||||
for (p = ssh_config_get_str_tok(&s, NULL);
|
||||
p != NULL && p[0] != '\0';
|
||||
p = ssh_config_get_str_tok(&s, NULL)) {
|
||||
@@ -1179,7 +1193,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
if (z == NULL) {
|
||||
z = strdup(p);
|
||||
}
|
||||
session->opts.config_hostname_only = true;
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, z);
|
||||
session->opts.config_hostname_only = false;
|
||||
free(z);
|
||||
}
|
||||
break;
|
||||
@@ -1668,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
|
||||
*/
|
||||
@@ -1694,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
|
||||
*/
|
||||
@@ -1732,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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -243,6 +243,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
|
||||
/* Get the server supported oids */
|
||||
rc = ssh_gssapi_server_oids(&supported);
|
||||
if (rc != SSH_OK) {
|
||||
gss_release_oid_set(&min_stat, &both_supported);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
@@ -730,7 +731,8 @@ int ssh_gssapi_check_client_config(ssh_session session)
|
||||
gssapi = calloc(1, sizeof(struct ssh_gssapi_struct));
|
||||
if (gssapi == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
return SSH_ERROR;
|
||||
ret = SSH_ERROR;
|
||||
break;
|
||||
}
|
||||
gssapi->server_creds = GSS_C_NO_CREDENTIAL;
|
||||
gssapi->client_creds = GSS_C_NO_CREDENTIAL;
|
||||
@@ -819,6 +821,11 @@ int ssh_gssapi_check_client_config(ssh_session session)
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER);
|
||||
|
||||
if (client_id != GSS_C_NO_NAME) {
|
||||
gss_release_name(&min_stat, &client_id);
|
||||
client_id = GSS_C_NO_NAME;
|
||||
}
|
||||
|
||||
SAFE_FREE(gssapi->canonic_user);
|
||||
SAFE_FREE(gssapi);
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ static int derive_hybrid_secret(ssh_session session,
|
||||
|
||||
rc = ssh_buffer_pack(combined_secret,
|
||||
"PP",
|
||||
MLKEM_SHARED_SECRET_SIZE,
|
||||
(size_t)MLKEM_SHARED_SECRET_SIZE,
|
||||
mlkem_shared_secret,
|
||||
ssh_string_len(ecdh_shared_secret),
|
||||
ssh_string_data(ecdh_shared_secret));
|
||||
@@ -244,7 +244,7 @@ int ssh_client_hybrid_mlkem_init(ssh_session session)
|
||||
"PP",
|
||||
ssh_string_len(crypto->mlkem_client_pubkey),
|
||||
ssh_string_data(crypto->mlkem_client_pubkey),
|
||||
CURVE25519_PUBKEY_SIZE,
|
||||
(size_t)CURVE25519_PUBKEY_SIZE,
|
||||
crypto->curve25519_client_pubkey);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
@@ -768,7 +768,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init)
|
||||
"PP",
|
||||
ssh_string_len(crypto->mlkem_ciphertext),
|
||||
ssh_string_data(crypto->mlkem_ciphertext),
|
||||
CURVE25519_PUBKEY_SIZE,
|
||||
(size_t)CURVE25519_PUBKEY_SIZE,
|
||||
crypto->curve25519_server_pubkey);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
|
||||
@@ -591,6 +591,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) {
|
||||
SSH_LOG(SSH_LOG_WARN,
|
||||
"GSSAPI(accept) integrity and mutual flags were not set");
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
goto error;
|
||||
}
|
||||
SSH_LOG(SSH_LOG_DEBUG, "token accepted");
|
||||
@@ -607,6 +608,7 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
"creating mic failed",
|
||||
maj_stat,
|
||||
min_stat);
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -621,15 +623,14 @@ int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
|
||||
output_token.length,
|
||||
(size_t)output_token.length,
|
||||
output_token.value);
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_release_buffer(&min_stat, &mic);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error_oom(session);
|
||||
ssh_buffer_reinit(session->out_buffer);
|
||||
goto error;
|
||||
}
|
||||
|
||||
gss_release_buffer(&min_stat, &output_token);
|
||||
gss_release_buffer(&min_stat, &mic);
|
||||
|
||||
rc = ssh_packet_send(session);
|
||||
if (rc == SSH_ERROR) {
|
||||
goto error;
|
||||
|
||||
@@ -1688,11 +1688,6 @@ int ssh_make_sessionid(ssh_session session)
|
||||
switch (session->next_crypto->kex_type) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
rc = ssh_buffer_pack(buf,
|
||||
"F",
|
||||
session->next_crypto->shared_secret,
|
||||
SHA512_DIGEST_LEN);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
#ifdef HAVE_MLKEM1024
|
||||
@@ -1919,9 +1914,6 @@ int ssh_generate_session_keys(ssh_session session)
|
||||
switch (session->next_crypto->kex_type) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
k_string = ssh_make_padded_bignum_string(crypto->shared_secret,
|
||||
crypto->digest_len);
|
||||
break;
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
case SSH_KEX_MLKEM768NISTP256_SHA256:
|
||||
#ifdef HAVE_MLKEM1024
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
src/legacy.c
10
src/legacy.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -507,3 +507,9 @@ LIBSSH_4_11_0 # Released
|
||||
sshsig_verify;
|
||||
} LIBSSH_4_10_0;
|
||||
|
||||
LIBSSH_AFTER_4_11_0
|
||||
{
|
||||
global:
|
||||
ssh_bind_config_parse_string;
|
||||
} LIBSSH_4_11_0;
|
||||
|
||||
|
||||
51
src/log.c
51
src/log.c
@@ -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)
|
||||
{
|
||||
|
||||
159
src/misc.c
159
src/misc.c
@@ -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)
|
||||
{
|
||||
@@ -2454,7 +2597,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
/* open first to avoid TOCTOU */
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to open a file %s for reading: %s",
|
||||
filename,
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
@@ -2464,7 +2607,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
/* Check the file is sensible for a configuration file */
|
||||
r = fstat(fd, &sb);
|
||||
if (r != 0) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to stat %s: %s",
|
||||
filename,
|
||||
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
@@ -2472,7 +2615,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
return NULL;
|
||||
}
|
||||
if ((sb.st_mode & S_IFMT) != S_IFREG) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"The file %s is not a regular file: skipping",
|
||||
filename);
|
||||
close(fd);
|
||||
@@ -2480,7 +2623,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
}
|
||||
|
||||
if ((size_t)sb.st_size > max_file_size) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"The file %s is too large (%jd MB > %zu MB): skipping",
|
||||
filename,
|
||||
(intmax_t)sb.st_size / 1024 / 1024,
|
||||
@@ -2491,7 +2634,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
|
||||
|
||||
f = fdopen(fd, "r");
|
||||
if (f == NULL) {
|
||||
SSH_LOG(SSH_LOG_RARE,
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"Failed to open a file %s for reading: %s",
|
||||
filename,
|
||||
ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -106,6 +107,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
|
||||
}
|
||||
}
|
||||
|
||||
if (src->opts.originalhost != NULL) {
|
||||
new->opts.originalhost = strdup(src->opts.originalhost);
|
||||
if (new->opts.originalhost == NULL) {
|
||||
ssh_free(new);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (src->opts.bindaddr != NULL) {
|
||||
new->opts.bindaddr = strdup(src->opts.bindaddr);
|
||||
if (new->opts.bindaddr == NULL) {
|
||||
@@ -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,
|
||||
@@ -369,8 +392,8 @@ int ssh_options_set_algo(ssh_session session,
|
||||
* default ssh directory.\n
|
||||
* \n
|
||||
* The ssh directory is used for files like known_hosts
|
||||
* and identity (private and public key). It may include
|
||||
* "%s" which will be replaced by the user home
|
||||
* and identity (private and public key). It may start
|
||||
* with ~ which will be replaced by the user home
|
||||
* directory.
|
||||
*
|
||||
* - SSH_OPTIONS_KNOWNHOSTS:
|
||||
@@ -718,18 +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;
|
||||
@@ -2401,6 +2469,15 @@ static int ssh_bind_set_algo(ssh_bind sshbind,
|
||||
* not a pointer when it should have been a pointer, or if
|
||||
* its a pointer to a pointer when it should have just been
|
||||
* a pointer), then the behaviour is undefined.
|
||||
*
|
||||
* @warning Options set via this function may be overridden if a
|
||||
* configuration file is parsed afterwards (e.g., by an
|
||||
* implicit call to ssh_bind_options_parse_config() inside
|
||||
* ssh_bind_listen(), or by a manual call to the same
|
||||
* function) and contains the same options.\n
|
||||
* It is the caller’s responsibility to ensure the correct
|
||||
* order of API calls if explicit options must take
|
||||
* precedence.
|
||||
*/
|
||||
int
|
||||
ssh_bind_options_set(ssh_bind sshbind,
|
||||
|
||||
@@ -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",
|
||||
|
||||
34
src/poll.c
34
src/poll.c
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
485
src/sftpserver.c
485
src/sftpserver.c
@@ -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;
|
||||
}
|
||||
@@ -489,7 +492,10 @@ sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending name %s, ID %" PRIu32,
|
||||
ssh_string_get_char(file),
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(1)) < 0 ||
|
||||
@@ -532,6 +538,7 @@ int sftp_reply_handle(sftp_client_message msg, ssh_string handle)
|
||||
ssh_log_hexdump("Sending handle:",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "packet ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_ssh_string(out, handle) < 0 ||
|
||||
@@ -565,7 +572,7 @@ int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr, ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
buffer_add_attributes(out, attr) < 0 ||
|
||||
@@ -653,7 +660,10 @@ int sftp_reply_names(sftp_client_message msg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending %d names, ID %" PRIu32,
|
||||
msg->attr_num,
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 ||
|
||||
@@ -705,8 +715,11 @@ sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status,
|
||||
ssh_string_get_char(s));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending status %d, message: %s, ID %" PRIu32,
|
||||
status,
|
||||
ssh_string_get_char(s),
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, htonl(status)) < 0 ||
|
||||
@@ -745,7 +758,10 @@ int sftp_reply_data(sftp_client_message msg, const void *data, int len)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending data, length: %d, ID %" PRIu32,
|
||||
len,
|
||||
msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u32(out, ntohl(len)) < 0 ||
|
||||
@@ -780,7 +796,7 @@ sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st)
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply, ID %" PRIu32, msg->id);
|
||||
|
||||
if (ssh_buffer_add_u32(out, msg->id) < 0 ||
|
||||
ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 ||
|
||||
@@ -810,7 +826,9 @@ int sftp_reply_version(sftp_client_message client_msg)
|
||||
ssh_buffer reply;
|
||||
int rc;
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet");
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Sending version packet, ID %" PRIu32,
|
||||
client_msg->id);
|
||||
|
||||
version = sftp->client_version;
|
||||
reply = ssh_buffer_new();
|
||||
@@ -819,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);
|
||||
@@ -933,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;
|
||||
@@ -1044,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);
|
||||
@@ -1088,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},
|
||||
};
|
||||
|
||||
@@ -1183,6 +1562,10 @@ process_read(sftp_client_message client_msg)
|
||||
ssh_log_hexdump("Processing read: handle:",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Offset %" PRIu64", length %" PRIu32,
|
||||
client_msg->offset,
|
||||
client_msg->len);
|
||||
|
||||
h = sftp_handle(sftp, handle);
|
||||
if (h != NULL && h->type == SFTP_FILE_HANDLE) {
|
||||
@@ -1241,6 +1624,10 @@ process_write(sftp_client_message client_msg)
|
||||
ssh_log_hexdump("Processing write: handle",
|
||||
(const unsigned char *)ssh_string_get_char(handle),
|
||||
ssh_string_len(handle));
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"Offset %" PRIu64", length %" PRIu32,
|
||||
client_msg->offset,
|
||||
client_msg->len);
|
||||
|
||||
h = sftp_handle(sftp, handle);
|
||||
if (h != NULL && h->type == SFTP_FILE_HANDLE) {
|
||||
@@ -1343,6 +1730,15 @@ process_opendir(sftp_client_message client_msg)
|
||||
}
|
||||
h->dirp = dir;
|
||||
h->name = strdup(dir_name);
|
||||
if (h->name == NULL) {
|
||||
free(h);
|
||||
closedir(dir);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "failed to duplicate directory name");
|
||||
sftp_reply_status(client_msg,
|
||||
SSH_FX_FAILURE,
|
||||
"Failed to allocate new handle");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
h->type = SFTP_DIR_HANDLE;
|
||||
handle_s = sftp_handle_alloc(client_msg->sftp, h);
|
||||
|
||||
@@ -1350,6 +1746,7 @@ process_opendir(sftp_client_message client_msg)
|
||||
sftp_reply_handle(client_msg, handle_s);
|
||||
ssh_string_free(handle_s);
|
||||
} else {
|
||||
SAFE_FREE(h->name);
|
||||
free(h);
|
||||
closedir(dir);
|
||||
sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available");
|
||||
@@ -1958,7 +2355,10 @@ dispatch_sftp_request(sftp_client_message sftp_msg)
|
||||
sftp_server_message_callback handler = NULL;
|
||||
uint8_t type = sftp_client_message_get_type(sftp_msg);
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type);
|
||||
SSH_LOG(SSH_LOG_PROTOCOL,
|
||||
"processing request type: %" PRIu8 ", ID %" PRIu32,
|
||||
type,
|
||||
sftp_msg->id);
|
||||
|
||||
for (int i = 0; message_handlers[i].cb != NULL; i++) {
|
||||
if (type == message_handlers[i].type) {
|
||||
@@ -2035,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),
|
||||
@@ -2057,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) {
|
||||
@@ -2066,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 */
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "libssh/sntrup761.h"
|
||||
#ifdef HAVE_SNTRUP761
|
||||
|
||||
#include "libssh/bignum.h"
|
||||
#include "libssh/buffer.h"
|
||||
#include "libssh/crypto.h"
|
||||
#include "libssh/dh.h"
|
||||
@@ -141,7 +140,7 @@ static int ssh_sntrup761x25519_build_k(ssh_session session)
|
||||
{
|
||||
unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE];
|
||||
unsigned char *k = ssk + SNTRUP761_SIZE;
|
||||
unsigned char hss[SHA512_DIGEST_LEN];
|
||||
void *shared_secret_data = NULL;
|
||||
int rc;
|
||||
|
||||
rc = ssh_curve25519_create_k(session, k);
|
||||
@@ -216,22 +215,27 @@ static int ssh_sntrup761x25519_build_k(ssh_session session)
|
||||
ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE);
|
||||
#endif
|
||||
|
||||
sha512(ssk, sizeof ssk, hss);
|
||||
|
||||
bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret);
|
||||
if (session->next_crypto->shared_secret == NULL) {
|
||||
ssh_string_burn(session->next_crypto->hybrid_shared_secret);
|
||||
ssh_string_free(session->next_crypto->hybrid_shared_secret);
|
||||
session->next_crypto->hybrid_shared_secret =
|
||||
ssh_string_new(SHA512_DIGEST_LEN);
|
||||
if (session->next_crypto->hybrid_shared_secret == NULL) {
|
||||
ssh_set_error_oom(session);
|
||||
rc = SSH_ERROR;
|
||||
goto cleanup;
|
||||
}
|
||||
shared_secret_data =
|
||||
ssh_string_data(session->next_crypto->hybrid_shared_secret);
|
||||
|
||||
sha512(ssk, sizeof ssk, shared_secret_data);
|
||||
|
||||
#ifdef DEBUG_CRYPTO
|
||||
ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret);
|
||||
ssh_log_hexdump("Shared secret key", shared_secret_data, SHA512_DIGEST_LEN);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
cleanup:
|
||||
ssh_burn(ssk, sizeof ssk);
|
||||
ssh_burn(hss, sizeof hss);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
38
src/socket.c
38
src/socket.c
@@ -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);
|
||||
@@ -1435,7 +1467,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
|
||||
session = s->session;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Connecting to host %s port %d user %s through ProxyJump",
|
||||
session->opts.host,
|
||||
session->opts.port,
|
||||
@@ -1515,7 +1547,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
|
||||
/* transferred to the jump_thread_data */
|
||||
jump_session = NULL;
|
||||
|
||||
SSH_LOG(SSH_LOG_INFO,
|
||||
SSH_LOG(SSH_LOG_DEBUG,
|
||||
"Starting proxy thread to host %s port %d user %s, callbacks=%p",
|
||||
jump_thread_data->next_jump->hostname,
|
||||
jump_thread_data->next_jump->port,
|
||||
|
||||
@@ -38,7 +38,6 @@ static struct ssh_threads_callbacks_struct *user_callbacks = NULL;
|
||||
/** @internal
|
||||
* @brief inits the threading with the backend cryptographic libraries
|
||||
*/
|
||||
|
||||
int ssh_threads_init(void)
|
||||
{
|
||||
static int threads_initialized = 0;
|
||||
@@ -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) {
|
||||
|
||||
@@ -259,6 +259,20 @@ else()
|
||||
set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found")
|
||||
endif()
|
||||
|
||||
find_program(TINYSSHD_EXECUTABLE
|
||||
NAMES
|
||||
tinysshd
|
||||
PATHS
|
||||
/sbin
|
||||
/usr/sbin
|
||||
/usr/local/sbin)
|
||||
|
||||
if (TINYSSHD_EXECUTABLE)
|
||||
message(STATUS "Found TinySSH server: ${TINYSSHD_EXECUTABLE}")
|
||||
else()
|
||||
message(STATUS "Could NOT find TinySSH server")
|
||||
endif()
|
||||
|
||||
find_program(SSHD_EXECUTABLE
|
||||
NAME
|
||||
sshd
|
||||
@@ -391,6 +405,14 @@ if (CLIENT_TESTING OR SERVER_TESTING)
|
||||
file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
|
||||
file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
|
||||
|
||||
# TINYSSH Key Setup
|
||||
set(TINYSSH_KEY_DIR "${CMAKE_CURRENT_BINARY_DIR}/etc/tinyssh")
|
||||
file(MAKE_DIRECTORY ${TINYSSH_KEY_DIR})
|
||||
|
||||
# Copy the TINYSSH hostkeys
|
||||
file(COPY keys/ed25519.pk DESTINATION ${TINYSSH_KEY_DIR})
|
||||
file(COPY keys/.ed25519.sk DESTINATION ${TINYSSH_KEY_DIR})
|
||||
|
||||
# Allow to auth with bob's public keys on alice and doe account
|
||||
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY)
|
||||
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY)
|
||||
@@ -419,6 +441,9 @@ if (CLIENT_TESTING OR SERVER_TESTING)
|
||||
file(READ keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub CONTENTS)
|
||||
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}")
|
||||
|
||||
# Create home directory for noneuser (for "none" authentication test)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/home/noneuser/.ssh)
|
||||
|
||||
file(READ keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub CONTENTS)
|
||||
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}")
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ if (WITH_PKCS11_URI)
|
||||
torture_auth_pkcs11)
|
||||
endif()
|
||||
|
||||
if (TINYSSHD_EXECUTABLE AND NCAT_EXECUTABLE)
|
||||
set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} torture_tinyssh)
|
||||
endif()
|
||||
|
||||
if (HAVE_PTHREAD)
|
||||
set(LIBSSH_CLIENT_TESTS
|
||||
${LIBSSH_CLIENT_TESTS}
|
||||
@@ -92,6 +96,12 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS})
|
||||
LINK_LIBRARIES ${TORTURE_LIBRARY} util
|
||||
)
|
||||
|
||||
if (_CLI_TEST STREQUAL "torture_tinyssh")
|
||||
target_compile_definitions(${_CLI_TEST} PRIVATE
|
||||
TINYSSH_KEYS_DIR="${TINYSSH_KEY_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (OSX)
|
||||
set_property(
|
||||
TEST
|
||||
|
||||
@@ -228,6 +228,44 @@ static void torture_auth_none_max_tries(void **state) {
|
||||
torture_update_sshd_config(state, "");
|
||||
}
|
||||
|
||||
static void torture_auth_none_success(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
const char *additional_config = "PermitEmptyPasswords yes\n"
|
||||
"PasswordAuthentication yes\n"
|
||||
"KbdInteractiveAuthentication no\n"
|
||||
"PubkeyAuthentication no\n"
|
||||
"AuthenticationMethods none\n";
|
||||
|
||||
ssh_session session = s->ssh.session;
|
||||
int rc;
|
||||
|
||||
torture_update_sshd_config(state, additional_config);
|
||||
|
||||
/* Use noneuser which has an empty password set in shadow.in
|
||||
* When PermitEmptyPasswords is yes and PasswordAuthentication is yes,
|
||||
* OpenSSH's userauth_none() internally calls mm_auth_password() with
|
||||
* an empty password, which succeeds for users with empty passwords.
|
||||
*/
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_NONEUSER);
|
||||
if (rc != SSH_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = ssh_connect(session);
|
||||
if (rc != SSH_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = ssh_userauth_none(session, NULL);
|
||||
assert_int_equal(rc, SSH_AUTH_SUCCESS);
|
||||
|
||||
cleanup:
|
||||
torture_update_sshd_config(state, "");
|
||||
if (rc != SSH_OK && rc != SSH_AUTH_SUCCESS) {
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
}
|
||||
}
|
||||
|
||||
static void torture_auth_pubkey(void **state) {
|
||||
struct torture_state *s = *state;
|
||||
@@ -1373,6 +1411,9 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_success,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_none_max_tries,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
@@ -1424,9 +1465,10 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_agent_identities_only_protected,
|
||||
agent_setup,
|
||||
agent_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
@@ -1436,15 +1478,17 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_types_ecdsa_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_types_ed25519_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
#ifdef WITH_FIDO2
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ecdsa,
|
||||
pubkey_setup,
|
||||
@@ -1456,9 +1500,10 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_auth_pubkey_rsa_key_size_nonblocking,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_auth_pubkey_skip_none,
|
||||
pubkey_setup,
|
||||
session_teardown),
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "libssh/callbacks.h"
|
||||
#include "libssh/libssh.h"
|
||||
#include <libssh/agent.h>
|
||||
#include "libssh/priv.h"
|
||||
|
||||
#include <errno.h>
|
||||
@@ -286,6 +287,96 @@ static void torture_auth_agent_forwarding(void **state)
|
||||
}
|
||||
}
|
||||
|
||||
static int agent_session_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
int verbosity = torture_libssh_verbosity();
|
||||
int rc;
|
||||
|
||||
s->ssh.ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.ssh.session);
|
||||
|
||||
rc = ssh_options_set(s->ssh.ssh.session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&verbosity);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
/* No callbacks needed — only talking to the local agent.
|
||||
* The group setup already started the agent and loaded keys.
|
||||
* Do NOT call torture_setup_ssh_agent here — that would spawn
|
||||
* a second agent and overwrite SSH_AUTH_SOCK. */
|
||||
s->ssh.ssh.cb_state = NULL;
|
||||
s->ssh.ssh.callbacks = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
ssh_key key = NULL;
|
||||
char *comment = NULL;
|
||||
uint32_t count_before = 0;
|
||||
uint32_t count_after = 0;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
|
||||
assert_true(ssh_agent_is_running(session));
|
||||
|
||||
count_before = ssh_agent_get_ident_count(session);
|
||||
|
||||
assert_true(count_before > 0);
|
||||
|
||||
key = ssh_agent_get_first_ident(session, &comment);
|
||||
assert_non_null(key);
|
||||
assert_non_null(comment);
|
||||
|
||||
rc = ssh_agent_remove_identity(session, key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
count_after = ssh_agent_get_ident_count(session);
|
||||
assert_int_equal(count_after, count_before - 1);
|
||||
|
||||
ssh_key_free(key);
|
||||
ssh_string_free_char(comment);
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity_negative(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
|
||||
/* NULL key should return SSH_ERROR */
|
||||
rc = ssh_agent_remove_identity(session, NULL);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
static void torture_agent_remove_identity_nonexistent(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
ssh_session session = s->ssh.ssh.session;
|
||||
ssh_key key = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(session);
|
||||
assert_true(ssh_agent_is_running(session));
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(key);
|
||||
|
||||
/* Key not in agent should fail */
|
||||
rc = ssh_agent_remove_identity(session, key);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
ssh_key_free(key);
|
||||
}
|
||||
|
||||
/* Session setup function that configures SSH agent */
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
@@ -300,7 +391,6 @@ static int session_setup(void **state)
|
||||
/* Create a new session */
|
||||
s->ssh.ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.ssh.session);
|
||||
|
||||
rc = ssh_options_set(s->ssh.ssh.session,
|
||||
SSH_OPTIONS_LOG_VERBOSITY,
|
||||
&verbosity);
|
||||
@@ -342,19 +432,32 @@ static int session_setup(void **state)
|
||||
int torture_run_tests(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_negative,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
|
||||
cmocka_unit_test_setup_teardown(torture_agent_remove_identity_nonexistent,
|
||||
agent_session_setup,
|
||||
session_teardown),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
/* Simplify the CMocka test filter handling */
|
||||
#if defined HAVE_CMOCKA_SET_TEST_FILTER
|
||||
cmocka_set_message_output(CM_OUTPUT_STDOUT);
|
||||
#endif
|
||||
|
||||
/* Apply test filtering */
|
||||
torture_filter_tests(tests);
|
||||
|
||||
rc = cmocka_run_group_tests(tests,
|
||||
@@ -365,5 +468,4 @@ int torture_run_tests(void)
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
328
tests/client/torture_tinyssh.c
Normal file
328
tests/client/torture_tinyssh.c
Normal file
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* This file is part of the SSH Library
|
||||
*
|
||||
* Copyright (c) 2026 by Your Bulitha Kawushika De Zoysa
|
||||
*
|
||||
* The SSH Library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* The SSH Library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the SSH Library; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
* MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "tests_config.h"
|
||||
|
||||
#define LIBSSH_STATIC
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/session.h"
|
||||
#include "torture.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TINYSSH_PIDFILE "tinyssh.pid"
|
||||
#define TINYSSH_PORT 22
|
||||
|
||||
/* TINYSSH Server Setup and Teardown */
|
||||
|
||||
static int tinyssh_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = NULL;
|
||||
char cmd[4096];
|
||||
char pid_path[1024];
|
||||
int rc;
|
||||
|
||||
torture_setup_socket_dir(state);
|
||||
s = *state;
|
||||
|
||||
snprintf(pid_path,
|
||||
sizeof(pid_path),
|
||||
"%s/%s",
|
||||
s->socket_dir,
|
||||
TINYSSH_PIDFILE);
|
||||
free(s->srv_pidfile);
|
||||
s->srv_pidfile = strdup(pid_path);
|
||||
if (s->srv_pidfile == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(cmd,
|
||||
sizeof(cmd),
|
||||
"%s -l %s %d -k -c \"%s %s -v %s\" "
|
||||
"> %s/tinyssh.log 2>&1 & echo $! > %s",
|
||||
NCAT_EXECUTABLE,
|
||||
TORTURE_SSH_SERVER,
|
||||
TINYSSH_PORT,
|
||||
TINYSSHD_EXECUTABLE,
|
||||
"",
|
||||
TINYSSH_KEYS_DIR,
|
||||
s->socket_dir,
|
||||
s->srv_pidfile);
|
||||
|
||||
SSH_LOG(SSH_LOG_DEBUG, "Executing: %s\n", cmd);
|
||||
|
||||
rc = system(cmd);
|
||||
if (rc != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = torture_wait_for_daemon(15);
|
||||
if (rc != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tinyssh_teardown(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
torture_terminate_process(s->srv_pidfile);
|
||||
torture_teardown_socket_dir(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* LIBSSH Client Setup and Teardown */
|
||||
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
int verbosity = torture_libssh_verbosity();
|
||||
bool process_config = false;
|
||||
int port = TINYSSH_PORT;
|
||||
struct passwd *pwd = NULL;
|
||||
int rc;
|
||||
|
||||
pwd = getpwnam("bob");
|
||||
assert_non_null(pwd);
|
||||
|
||||
rc = setuid(pwd->pw_uid);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
s->ssh.session = ssh_new();
|
||||
assert_non_null(s->ssh.session);
|
||||
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT, &port);
|
||||
ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "bob");
|
||||
ssh_options_set(s->ssh.session,
|
||||
SSH_OPTIONS_PROCESS_CONFIG,
|
||||
&process_config);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int session_teardown(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
if (s->ssh.session) {
|
||||
ssh_disconnect(s->ssh.session);
|
||||
ssh_free(s->ssh.session);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Algorithms Helper */
|
||||
|
||||
static void test_specific_algorithm(ssh_session session,
|
||||
const char *kex,
|
||||
const char *cipher,
|
||||
const char *hostkey,
|
||||
int expected_rc)
|
||||
{
|
||||
int rc;
|
||||
char data[256];
|
||||
size_t len_to_test[] = {1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15,
|
||||
16, 20, 31, 32, 33, 63, 64, 65, 100, 127, 128};
|
||||
unsigned int i;
|
||||
|
||||
/* Set Key Exchange */
|
||||
if (kex != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
/* Set Ciphers */
|
||||
if (cipher != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher);
|
||||
assert_ssh_return_code(session, rc);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
/* Set Hostkey */
|
||||
if (hostkey != NULL) {
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey);
|
||||
assert_ssh_return_code(session, rc);
|
||||
}
|
||||
|
||||
rc = ssh_connect(session);
|
||||
|
||||
if (expected_rc == SSH_OK) {
|
||||
assert_ssh_return_code(session, rc);
|
||||
|
||||
if (cipher != NULL) {
|
||||
const char *used_cipher = ssh_get_cipher_out(session);
|
||||
assert_non_null(used_cipher);
|
||||
assert_string_equal(used_cipher, cipher);
|
||||
}
|
||||
|
||||
if (hostkey != NULL) {
|
||||
ssh_key pubkey = NULL;
|
||||
const char *type_str = NULL;
|
||||
|
||||
rc = ssh_get_server_publickey(session, &pubkey);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(pubkey);
|
||||
|
||||
type_str = ssh_key_type_to_char(ssh_key_type(pubkey));
|
||||
assert_non_null(type_str);
|
||||
assert_string_equal(type_str, hostkey);
|
||||
ssh_key_free(pubkey);
|
||||
}
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) {
|
||||
memset(data, 'A', len_to_test[i]);
|
||||
ssh_send_ignore(session, data);
|
||||
ssh_handle_packets(session, 50);
|
||||
}
|
||||
|
||||
rc = ssh_userauth_none(session, NULL);
|
||||
if (rc != SSH_OK) {
|
||||
rc = ssh_get_error_code(session);
|
||||
assert_int_equal(rc, SSH_REQUEST_DENIED);
|
||||
}
|
||||
|
||||
} else {
|
||||
assert_int_not_equal(rc, SSH_OK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test Cases */
|
||||
|
||||
static void torture_tinyssh_curve25519(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"curve25519-sha256",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_curve25519_libssh(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"curve25519-sha256@libssh.org",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_sntrup761(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"sntrup761x25519-sha512@openssh.com",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_chacha20(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
NULL,
|
||||
"chacha20-poly1305@openssh.com",
|
||||
NULL,
|
||||
SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_neg_cipher(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
|
||||
/* TinySSH does not support older ciphers like aes128-cbc.*/
|
||||
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
NULL,
|
||||
"aes128-cbc",
|
||||
NULL,
|
||||
SSH_ERROR);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_hostkey_ed25519(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
test_specific_algorithm(s->ssh.session, NULL, NULL, "ssh-ed25519", SSH_OK);
|
||||
}
|
||||
|
||||
static void torture_tinyssh_neg_kex(void **state)
|
||||
{
|
||||
struct torture_state *s = *state;
|
||||
|
||||
/* TinySSH does not support legacy Diffie-Hellman groups or NIST curves.*/
|
||||
|
||||
test_specific_algorithm(s->ssh.session,
|
||||
"diffie-hellman-group1-sha1",
|
||||
NULL,
|
||||
NULL,
|
||||
SSH_ERROR);
|
||||
}
|
||||
|
||||
int torture_run_tests(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519_libssh,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_sntrup761,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_hostkey_ed25519,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_chacha20,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_cipher,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_kex,
|
||||
session_setup,
|
||||
session_teardown),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
torture_filter_tests(tests);
|
||||
rc = cmocka_run_group_tests(tests, tinyssh_setup, tinyssh_teardown);
|
||||
|
||||
ssh_finalize();
|
||||
return rc;
|
||||
}
|
||||
@@ -2,3 +2,4 @@ bob:secret:sshd
|
||||
alice:secret:sshd
|
||||
charlie:secret:sshd
|
||||
doe:secret:sshd
|
||||
noneuser::sshd
|
||||
|
||||
@@ -3,6 +3,7 @@ alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh
|
||||
charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh
|
||||
doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/bin/sh
|
||||
frank:x:5003:9000:doe gecos:@HOMEDIR@/frank:/bin/sh
|
||||
noneuser:x:5004:9000:noneuser gecos:@HOMEDIR@/noneuser:/bin/sh
|
||||
sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin
|
||||
nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false
|
||||
root:x:0:0:root gecos:@HOMEDIR@:/bin/false
|
||||
|
||||
@@ -2,3 +2,4 @@ alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFl
|
||||
bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0:::::
|
||||
noneuser:::0:::::
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
project(fuzzing CXX)
|
||||
|
||||
# Build SSH server mock helper as object library
|
||||
add_library(ssh_server_mock_obj OBJECT ssh_server_mock.c)
|
||||
target_link_libraries(ssh_server_mock_obj PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
macro(fuzzer name)
|
||||
add_executable(${name} ${name}.c)
|
||||
target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
# Add ssh_server_mock dependency for scp and sftp fuzzers
|
||||
if (${name} STREQUAL "ssh_scp_fuzzer" OR ${name} STREQUAL "ssh_sftp_fuzzer")
|
||||
target_sources(${name} PRIVATE $<TARGET_OBJECTS:ssh_server_mock_obj>)
|
||||
target_link_libraries(${name} PRIVATE pthread)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set_target_properties(${name}
|
||||
PROPERTIES
|
||||
@@ -33,8 +44,10 @@ fuzzer(ssh_client_config_fuzzer)
|
||||
fuzzer(ssh_known_hosts_fuzzer)
|
||||
fuzzer(ssh_privkey_fuzzer)
|
||||
fuzzer(ssh_pubkey_fuzzer)
|
||||
fuzzer(ssh_sftp_attr_fuzzer)
|
||||
fuzzer(ssh_sshsig_fuzzer)
|
||||
if (WITH_SERVER)
|
||||
fuzzer(ssh_scp_fuzzer)
|
||||
fuzzer(ssh_server_fuzzer)
|
||||
fuzzer(ssh_bind_config_fuzzer)
|
||||
endif (WITH_SERVER)
|
||||
|
||||
@@ -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
206
tests/fuzz/ssh_scp_fuzzer.c
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/scp.h>
|
||||
|
||||
#include "nallocinc.c"
|
||||
#include "ssh_server_mock.h"
|
||||
|
||||
static void _fuzz_finalize(void)
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
nalloc_init(*argv[0]);
|
||||
ssh_init();
|
||||
atexit(_fuzz_finalize);
|
||||
ssh_mock_write_hostkey(SSH_MOCK_HOSTKEY_PATH);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function to test one cipher/HMAC combination */
|
||||
static int test_scp_with_cipher(const uint8_t *data,
|
||||
size_t size,
|
||||
const char *cipher,
|
||||
const char *hmac)
|
||||
{
|
||||
int socket_fds[2] = {-1, -1};
|
||||
ssh_session client_session = NULL;
|
||||
ssh_scp scp = NULL, scp_recursive = NULL;
|
||||
char buf[256] = {0};
|
||||
pthread_t srv_thread;
|
||||
|
||||
/* Configure mock SSH server with fuzzer data */
|
||||
struct ssh_mock_server_config server_config = {
|
||||
.protocol_data = data,
|
||||
.protocol_data_size = size,
|
||||
.exec_callback = ssh_mock_send_raw_data,
|
||||
.subsystem_callback = NULL,
|
||||
.callback_userdata = NULL,
|
||||
.cipher = cipher,
|
||||
.hmac = hmac,
|
||||
.server_socket = -1,
|
||||
.client_socket = -1,
|
||||
.server_ready = false,
|
||||
.server_error = false,
|
||||
};
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
server_config.server_socket = socket_fds[0];
|
||||
server_config.client_socket = socket_fds[1];
|
||||
|
||||
if (ssh_mock_server_start(&server_config, &srv_thread) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
client_session = ssh_new();
|
||||
if (client_session == NULL) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
/* Configure client with specified cipher/HMAC */
|
||||
ssh_options_set(client_session, SSH_OPTIONS_FD, &socket_fds[1]);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HOST, "localhost");
|
||||
ssh_options_set(client_session, SSH_OPTIONS_USER, "fuzz");
|
||||
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_C_S, cipher);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_CIPHERS_S_C, cipher);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HMAC_C_S, hmac);
|
||||
ssh_options_set(client_session, SSH_OPTIONS_HMAC_S_C, hmac);
|
||||
|
||||
/* Set timeout for operations (1 second) */
|
||||
long timeout = 1;
|
||||
ssh_options_set(client_session, SSH_OPTIONS_TIMEOUT, &timeout);
|
||||
|
||||
if (ssh_connect(client_session) != SSH_OK) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (ssh_userauth_none(client_session, NULL) != SSH_AUTH_SUCCESS) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
scp = ssh_scp_new(client_session, SSH_SCP_READ, "/tmp/fuzz");
|
||||
if (scp == NULL) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (ssh_scp_init(scp) != SSH_OK) {
|
||||
goto cleanup_thread;
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
size_t copy_size = size < sizeof(buf) ? size : sizeof(buf);
|
||||
memcpy(buf, data, copy_size);
|
||||
}
|
||||
|
||||
/* Fuzz all SCP API functions in read mode */
|
||||
ssh_scp_pull_request(scp);
|
||||
ssh_scp_request_get_filename(scp);
|
||||
ssh_scp_request_get_permissions(scp);
|
||||
ssh_scp_request_get_size64(scp);
|
||||
ssh_scp_request_get_size(scp);
|
||||
ssh_scp_request_get_warning(scp);
|
||||
ssh_scp_accept_request(scp);
|
||||
ssh_scp_deny_request(scp, "Denied by fuzzer");
|
||||
ssh_scp_read(scp, buf, sizeof(buf));
|
||||
|
||||
/* Final fuzz of scp pull request after all the calls */
|
||||
ssh_scp_pull_request(scp);
|
||||
|
||||
/* Fuzz SCP in write/upload + recursive directory mode. */
|
||||
scp_recursive = ssh_scp_new(client_session,
|
||||
SSH_SCP_WRITE | SSH_SCP_RECURSIVE,
|
||||
"/tmp/fuzz-recursive");
|
||||
if (scp_recursive != NULL) {
|
||||
if (ssh_scp_init(scp_recursive) == SSH_OK) {
|
||||
ssh_scp_push_directory(scp_recursive, "fuzz-dir", 0755);
|
||||
ssh_scp_push_file(scp_recursive, "fuzz-file", sizeof(buf), 0644);
|
||||
ssh_scp_write(scp_recursive, buf, sizeof(buf));
|
||||
ssh_scp_leave_directory(scp_recursive);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_thread:
|
||||
pthread_join(srv_thread, NULL);
|
||||
|
||||
cleanup:
|
||||
if (scp_recursive != NULL) {
|
||||
ssh_scp_close(scp_recursive);
|
||||
ssh_scp_free(scp_recursive);
|
||||
}
|
||||
if (scp) {
|
||||
ssh_scp_close(scp);
|
||||
ssh_scp_free(scp);
|
||||
}
|
||||
if (client_session) {
|
||||
ssh_disconnect(client_session);
|
||||
ssh_free(client_session);
|
||||
}
|
||||
if (socket_fds[0] >= 0)
|
||||
close(socket_fds[0]);
|
||||
if (socket_fds[1] >= 0)
|
||||
close(socket_fds[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
assert(nalloc_start(data, size) > 0);
|
||||
|
||||
/* Test all cipher/HMAC combinations exhaustively */
|
||||
const char *ciphers[] = {
|
||||
"none",
|
||||
"aes128-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-cbc",
|
||||
};
|
||||
|
||||
const char *hmacs[] = {
|
||||
"none",
|
||||
"hmac-sha1",
|
||||
"hmac-sha2-256",
|
||||
};
|
||||
|
||||
int num_ciphers = sizeof(ciphers) / sizeof(ciphers[0]);
|
||||
int num_hmacs = sizeof(hmacs) / sizeof(hmacs[0]);
|
||||
|
||||
for (int i = 0; i < num_ciphers; i++) {
|
||||
for (int j = 0; j < num_hmacs; j++) {
|
||||
test_scp_with_cipher(data, size, ciphers[i], hmacs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
nalloc_end();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
C0644 50 ../../../etc/passwd
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 dir/file.txt
|
||||
@@ -0,0 +1 @@
|
||||
C 100 test
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 ..
|
||||
@@ -0,0 +1 @@
|
||||
C0755 1024 executable.sh
|
||||
@@ -0,0 +1 @@
|
||||
C0644 999999999999 huge.dat
|
||||
@@ -0,0 +1 @@
|
||||
T1234567890 0 1234567890 0
|
||||
@@ -0,0 +1 @@
|
||||
C0644 100 test.txt
|
||||
@@ -0,0 +1 @@
|
||||
Warning: Test warning
|
||||
@@ -0,0 +1 @@
|
||||
C0644 10 .
|
||||
@@ -0,0 +1 @@
|
||||
E
|
||||
@@ -0,0 +1 @@
|
||||
Error: Test error
|
||||
@@ -0,0 +1 @@
|
||||
Xunknown command
|
||||
@@ -0,0 +1 @@
|
||||
C0644 test
|
||||
@@ -0,0 +1 @@
|
||||
D0755 0 mydir
|
||||
@@ -0,0 +1 @@
|
||||
C0644 abc test
|
||||
262
tests/fuzz/ssh_server_mock.c
Normal file
262
tests/fuzz/ssh_server_mock.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ssh_server_mock.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/server.h>
|
||||
|
||||
/* Fixed ed25519 key for all mock servers */
|
||||
const char *ssh_mock_ed25519_key_pem =
|
||||
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
||||
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
|
||||
"QyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zAAAAJgbsYq1G7GK\n"
|
||||
"tQAAAAtzc2gtZWQyNTUxOQAAACBpFO8/JfYlIqg6+vqx1vDKWDqxJHxw4tBqnQfiOjf2zA\n"
|
||||
"AAAEAkGaLvQwKMbGVRk2M8cz7gqWvpBKuHkuekJxIBQrUJl2kU7z8l9iUiqDr6+rHW8MpY\n"
|
||||
"OrEkfHDi0GqdB+I6N/bMAAAAEGZ1enotZWQyNTUxOS1rZXkBAgMEBQ==\n"
|
||||
"-----END OPENSSH PRIVATE KEY-----\n";
|
||||
|
||||
/* Internal server session data */
|
||||
struct mock_session_data {
|
||||
ssh_channel channel;
|
||||
struct ssh_mock_server_config *config;
|
||||
};
|
||||
|
||||
/* Auth callback - always accepts "none" auth */
|
||||
static int mock_auth_none(ssh_session session, const char *user, void *userdata)
|
||||
{
|
||||
(void)session;
|
||||
(void)user;
|
||||
(void)userdata;
|
||||
return SSH_AUTH_SUCCESS;
|
||||
}
|
||||
|
||||
/* Channel open callback */
|
||||
static ssh_channel mock_channel_open(ssh_session session, void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
sdata->channel = ssh_channel_new(session);
|
||||
return sdata->channel;
|
||||
}
|
||||
|
||||
/* Exec request callback - for SCP */
|
||||
static int mock_channel_exec(ssh_session session,
|
||||
ssh_channel channel,
|
||||
const char *command,
|
||||
void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
(void)session;
|
||||
(void)command;
|
||||
|
||||
if (sdata->config->exec_callback) {
|
||||
return sdata->config->exec_callback(channel,
|
||||
sdata->config->protocol_data,
|
||||
sdata->config->protocol_data_size,
|
||||
sdata->config->callback_userdata);
|
||||
}
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Subsystem request callback - for SFTP */
|
||||
static int mock_channel_subsystem(ssh_session session,
|
||||
ssh_channel channel,
|
||||
const char *subsystem,
|
||||
void *userdata)
|
||||
{
|
||||
struct mock_session_data *sdata = (struct mock_session_data *)userdata;
|
||||
(void)session;
|
||||
(void)subsystem;
|
||||
|
||||
if (sdata->config->subsystem_callback) {
|
||||
return sdata->config->subsystem_callback(
|
||||
channel,
|
||||
sdata->config->protocol_data,
|
||||
sdata->config->protocol_data_size,
|
||||
sdata->config->callback_userdata);
|
||||
}
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Server thread implementation */
|
||||
static void *server_thread_func(void *arg)
|
||||
{
|
||||
struct ssh_mock_server_config *config =
|
||||
(struct ssh_mock_server_config *)arg;
|
||||
ssh_bind sshbind = NULL;
|
||||
ssh_session session = NULL;
|
||||
ssh_event event = NULL;
|
||||
struct mock_session_data sdata = {0};
|
||||
sdata.config = config;
|
||||
|
||||
struct ssh_server_callbacks_struct server_cb = {
|
||||
.userdata = &sdata,
|
||||
.auth_none_function = mock_auth_none,
|
||||
.channel_open_request_session_function = mock_channel_open,
|
||||
};
|
||||
|
||||
struct ssh_channel_callbacks_struct channel_cb = {
|
||||
.userdata = &sdata,
|
||||
.channel_exec_request_function = mock_channel_exec,
|
||||
.channel_subsystem_request_function = mock_channel_subsystem,
|
||||
};
|
||||
|
||||
bool no = false;
|
||||
|
||||
sshbind = ssh_bind_new();
|
||||
if (sshbind == NULL) {
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
ssh_bind_free(sshbind);
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *cipher = config->cipher ? config->cipher : "aes128-ctr";
|
||||
const char *hmac = config->hmac ? config->hmac : "hmac-sha1";
|
||||
|
||||
ssh_bind_options_set(sshbind,
|
||||
SSH_BIND_OPTIONS_HOSTKEY,
|
||||
SSH_MOCK_HOSTKEY_PATH);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, cipher);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, cipher);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, hmac);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, hmac);
|
||||
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no);
|
||||
|
||||
ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE);
|
||||
ssh_callbacks_init(&server_cb);
|
||||
ssh_set_server_callbacks(session, &server_cb);
|
||||
|
||||
if (ssh_bind_accept_fd(sshbind, session, config->server_socket) != SSH_OK) {
|
||||
ssh_free(session);
|
||||
ssh_bind_free(sshbind);
|
||||
config->server_error = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config->server_ready = true;
|
||||
|
||||
event = ssh_event_new();
|
||||
if (event == NULL) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
ssh_bind_free(sshbind);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ssh_handle_key_exchange(session) == SSH_OK) {
|
||||
ssh_event_add_session(event, session);
|
||||
|
||||
for (int i = 0; i < 50 && !sdata.channel; i++) {
|
||||
ssh_event_dopoll(event, 1);
|
||||
}
|
||||
|
||||
if (sdata.channel) {
|
||||
ssh_callbacks_init(&channel_cb);
|
||||
ssh_set_channel_callbacks(sdata.channel, &channel_cb);
|
||||
|
||||
int max_iterations = 30;
|
||||
for (int iter = 0; iter < max_iterations &&
|
||||
!ssh_channel_is_closed(sdata.channel) &&
|
||||
!ssh_channel_is_eof(sdata.channel);
|
||||
iter++) {
|
||||
if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event)
|
||||
ssh_event_free(event);
|
||||
if (session) {
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
}
|
||||
if (sshbind)
|
||||
ssh_bind_free(sshbind);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Public API - start mock SSH server */
|
||||
int ssh_mock_server_start(struct ssh_mock_server_config *config,
|
||||
pthread_t *thread)
|
||||
{
|
||||
if (!config || !thread)
|
||||
return -1;
|
||||
|
||||
config->server_ready = false;
|
||||
config->server_error = false;
|
||||
|
||||
if (pthread_create(thread, NULL, server_thread_func, config) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 50 && !config->server_ready && !config->server_error;
|
||||
i++) {
|
||||
usleep(100);
|
||||
}
|
||||
|
||||
return config->server_error ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Generic protocol callback - sends raw fuzzer data for any protocol */
|
||||
int ssh_mock_send_raw_data(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata)
|
||||
{
|
||||
(void)userdata;
|
||||
|
||||
ssh_channel target_channel = (ssh_channel)channel;
|
||||
|
||||
/* Send raw fuzzer data - let protocol parser interpret it */
|
||||
if (size > 0) {
|
||||
ssh_channel_write(target_channel, data, size);
|
||||
}
|
||||
|
||||
/* Close channel to signal completion */
|
||||
ssh_channel_send_eof(target_channel);
|
||||
ssh_channel_close(target_channel);
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
/* Write fixed ed25519 host key to file */
|
||||
int ssh_mock_write_hostkey(const char *path)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (fp == NULL)
|
||||
return -1;
|
||||
|
||||
size_t len = strlen(ssh_mock_ed25519_key_pem);
|
||||
size_t nwritten = fwrite(ssh_mock_ed25519_key_pem, 1, len, fp);
|
||||
fclose(fp);
|
||||
|
||||
return (nwritten == len) ? 0 : -1;
|
||||
}
|
||||
60
tests/fuzz/ssh_server_mock.h
Normal file
60
tests/fuzz/ssh_server_mock.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef SSH_SERVER_MOCK_H
|
||||
#define SSH_SERVER_MOCK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Server callback type */
|
||||
typedef int (*ssh_mock_callback_fn)(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata);
|
||||
|
||||
/* Mock server configuration */
|
||||
struct ssh_mock_server_config {
|
||||
const uint8_t *protocol_data;
|
||||
size_t protocol_data_size;
|
||||
ssh_mock_callback_fn exec_callback;
|
||||
ssh_mock_callback_fn subsystem_callback;
|
||||
void *callback_userdata;
|
||||
const char *cipher;
|
||||
const char *hmac;
|
||||
int server_socket;
|
||||
int client_socket;
|
||||
bool server_ready;
|
||||
bool server_error;
|
||||
};
|
||||
|
||||
/* Public API functions */
|
||||
int ssh_mock_server_start(struct ssh_mock_server_config *config,
|
||||
pthread_t *thread);
|
||||
int ssh_mock_send_raw_data(void *channel,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *userdata);
|
||||
int ssh_mock_write_hostkey(const char *path);
|
||||
|
||||
/* Fixed ed25519 key constant */
|
||||
extern const char *ssh_mock_ed25519_key_pem;
|
||||
|
||||
/* Centralized hostkey path used by all mock servers */
|
||||
#define SSH_MOCK_HOSTKEY_PATH "/tmp/libssh_mock_fuzz_key"
|
||||
|
||||
#endif
|
||||
133
tests/fuzz/ssh_sftp_attr_fuzzer.c
Normal file
133
tests/fuzz/ssh_sftp_attr_fuzzer.c
Normal file
@@ -0,0 +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 "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;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user