mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-03-24 20:40:09 +09:00
Compare commits
33 Commits
6a5e298cec
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
729a44e121 | ||
|
|
051ac812db | ||
|
|
01772c4f79 | ||
|
|
9f7c596ca5 | ||
|
|
34bbb48561 | ||
|
|
f060583d6f | ||
|
|
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 |
@@ -784,8 +784,6 @@ coverity:
|
||||
- mkdir obj && cd obj
|
||||
only:
|
||||
- branches@libssh/libssh-mirror
|
||||
- branches@cryptomilk/libssh-mirror
|
||||
- branches@jjelen/libssh-mirror
|
||||
|
||||
# TODO add -DFUZZ_TESTING=ON clang cant find _LLVMFuzzerInitialize on arm64
|
||||
macos-m1:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* This is a sample implementation of a libssh based SSH server */
|
||||
/* This is a sample implementation of a libssh based SFTP server */
|
||||
/*
|
||||
Copyright 2014 Audrius Butkevicius
|
||||
|
||||
@@ -9,6 +9,28 @@ domain. This does not apply to the rest of the library though, but it is
|
||||
allowed to cut-and-paste working code from this file to any license of
|
||||
program.
|
||||
The goal is to show the API in action.
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
|
||||
This is not a production-ready SFTP server implementation. While it demonstrates
|
||||
how an SFTP server can be implemented on the SFTP layer and integrated into
|
||||
existing SSH server, it lacks many steps int the authentication and
|
||||
session establishment!
|
||||
|
||||
It allows to log in any user with hardcoded credentials below or with public
|
||||
key provided from authorized keys file.
|
||||
|
||||
The resulting SFTP session keeps running under original user who runs the
|
||||
example server and therefore the SFTP session has access to all files that are
|
||||
accessible to the user running the server.
|
||||
|
||||
Real-world servers should at very least switch the user to unprivileged one
|
||||
after authentication using setuid(). If some more restrictions are needed,
|
||||
generally limiting what files should and should not be accessible, it is
|
||||
recommended to use chroot() as handling symlinks can be tricky in the SFTP
|
||||
callbacks.
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -10,6 +10,23 @@ allowed to cut-and-paste working code from this file to any license of
|
||||
program.
|
||||
The goal is to show the API in action. It's not a reference on how terminal
|
||||
clients must be made or how a client should react.
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
|
||||
This is not a production-ready SSH server implementation. While it demonstrates
|
||||
how an SSH server can be implemented, it lacks many steps during
|
||||
the authentication and session establishment!
|
||||
|
||||
It allows to log in any user with hardcoded credentials below or with public
|
||||
key provided from authorized keys file.
|
||||
|
||||
The resulting session keeps running under original user who runs the example
|
||||
server and therefore it retains the same permissions.
|
||||
|
||||
Real-world servers should at very least switch the user to unprivileged one
|
||||
after authentication using setuid().
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -9,6 +9,23 @@ domain. This does not apply to the rest of the library though, but it is
|
||||
allowed to cut-and-paste working code from this file to any license of
|
||||
program.
|
||||
The goal is to show the API in action.
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
|
||||
This is not a production-ready SSH server implementation. While it demonstrates
|
||||
how an SSH server can be implemented, it lacks many steps during
|
||||
the authentication and session establishment!
|
||||
|
||||
It allows to log in any user with hardcoded credentials below or with public
|
||||
key provided from authorized keys file.
|
||||
|
||||
The resulting session keeps running under original user who runs the example
|
||||
server and therefore it retains the same permissions.
|
||||
|
||||
Real-world servers should at very least switch the user to unprivileged one
|
||||
after authentication using setuid().
|
||||
|
||||
!!! WARNING / ACHTUNG !!!
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -176,6 +176,17 @@ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session,
|
||||
ssh_string ssh_agent_sign_data(ssh_session session,
|
||||
const ssh_key pubkey,
|
||||
struct ssh_buffer_struct *data);
|
||||
/**
|
||||
* @brief Remove an identity from the SSH agent.
|
||||
*
|
||||
* @param session The SSH session.
|
||||
* @param key The public key to remove.
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
|
||||
*/
|
||||
int ssh_agent_remove_identity(ssh_session session,
|
||||
const ssh_key key);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -57,10 +57,11 @@ enum ssh_bind_config_opcode_e {
|
||||
BIND_CFG_MAX /* Keep this one last in the list */
|
||||
};
|
||||
|
||||
/* @brief Parse configuration file and set the options to the given ssh_bind
|
||||
/**
|
||||
* @brief Parse configuration file and set the options to the given ssh_bind
|
||||
*
|
||||
* @params[in] sshbind The ssh_bind context to be configured
|
||||
* @params[in] filename The path to the configuration file
|
||||
* @param[in] sshbind The ssh_bind context to be configured
|
||||
* @param[in] filename The path to the configuration file
|
||||
*
|
||||
* @returns 0 on successful parsing the configuration file, -1 on error
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -130,14 +130,15 @@ extern "C" {
|
||||
/* SSH Key Functions */
|
||||
void ssh_key_clean (ssh_key key);
|
||||
|
||||
const char *
|
||||
ssh_key_get_signature_algorithm(ssh_session session,
|
||||
enum ssh_keytypes_e type);
|
||||
enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name);
|
||||
const char *ssh_key_get_signature_algorithm(ssh_session session,
|
||||
enum ssh_keytypes_e type);
|
||||
enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type);
|
||||
enum ssh_digest_e ssh_key_type_to_hash(ssh_session session,
|
||||
enum ssh_keytypes_e type);
|
||||
enum ssh_digest_e ssh_key_hash_from_name(const char *name);
|
||||
|
||||
int ssh_key_type_and_hash_from_signature_name(const char *name,
|
||||
enum ssh_keytypes_e *type,
|
||||
enum ssh_digest_e *hash_type);
|
||||
|
||||
#define is_ecdsa_key_type(t) \
|
||||
((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
#ifndef _SCP_H
|
||||
#define _SCP_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
|
||||
enum ssh_scp_states {
|
||||
SSH_SCP_NEW, //Data structure just created
|
||||
SSH_SCP_WRITE_INITED, //Gave our intention to write
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
@@ -392,7 +459,6 @@ LIBSSH_API sftp_session sftp_new(ssh_session session);
|
||||
*/
|
||||
LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Close and deallocate a sftp session.
|
||||
*
|
||||
@@ -1582,7 +1648,9 @@ LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle);
|
||||
#define SFTP_EXTENDED SSH_FXP_EXTENDED
|
||||
|
||||
/* openssh flags */
|
||||
/** @brief statvfs flag: file system is mounted read-only. */
|
||||
#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */
|
||||
/** @brief statvfs flag: file system does not support setuid/setgid. */
|
||||
#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
22
src/auth.c
22
src/auth.c
@@ -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)
|
||||
{
|
||||
|
||||
@@ -680,8 +680,8 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
* @brief Parse configuration string and set the options to the given bind
|
||||
* session
|
||||
*
|
||||
* @params[in] bind The ssh bind session
|
||||
* @params[in] input Null terminated string containing the configuration
|
||||
* @param[in] bind The ssh bind session
|
||||
* @param[in] input Null terminated string containing the configuration
|
||||
*
|
||||
* @warning Options set via this function may be overridden if a configuration
|
||||
* file is parsed afterwards (e.g., by an implicit call to
|
||||
@@ -690,7 +690,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
|
||||
* It is the caller’s responsibility to ensure the correct order of
|
||||
* API calls if explicit options must take precedence.
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* @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)
|
||||
|
||||
16
src/buffer.c
16
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
|
||||
|
||||
@@ -964,9 +969,12 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer,
|
||||
|
||||
/** @internal
|
||||
* @brief Add multiple values in a buffer on a single function call
|
||||
*
|
||||
* @param[in] buffer The buffer to add to
|
||||
* @param[in] format A format string of arguments.
|
||||
* @param[in] argc Number of arguments passed after format.
|
||||
* @param[in] ap A va_list of arguments.
|
||||
*
|
||||
* @returns SSH_OK on success
|
||||
* SSH_ERROR on error
|
||||
* @see ssh_buffer_add_format() for format list values.
|
||||
@@ -1112,6 +1120,9 @@ ssh_buffer_pack_va(struct ssh_buffer_struct *buffer,
|
||||
* 'P': size_t, void * (len of data, pointer to data)
|
||||
* only pushes data.
|
||||
* 'B': bignum (pushed as SSH string)
|
||||
* @param[in] argc Number of arguments passed after format.
|
||||
* @param[in] ... Arguments as described by the format string.
|
||||
*
|
||||
* @returns SSH_OK on success
|
||||
* SSH_ERROR on error
|
||||
* @warning when using 'P' with a constant size (e.g. 8), do not
|
||||
@@ -1148,7 +1159,9 @@ int _ssh_buffer_pack(struct ssh_buffer_struct *buffer,
|
||||
* @brief Get multiple values from a buffer on a single function call
|
||||
* @param[in] buffer The buffer to get from
|
||||
* @param[in] format A format string of arguments.
|
||||
* @param[in] argc Number of arguments passed in the va_list.
|
||||
* @param[in] ap A va_list of arguments.
|
||||
*
|
||||
* @returns SSH_OK on success
|
||||
* SSH_ERROR on error
|
||||
* @see ssh_buffer_get_format() for format list values.
|
||||
@@ -1411,6 +1424,9 @@ cleanup:
|
||||
* 'P': size_t, void ** (len of data, pointer to data)
|
||||
* only pulls data.
|
||||
* 'B': bignum * (pulled as SSH string)
|
||||
* @param[in] argc Number of arguments passed after format.
|
||||
* @param[in] ... Arguments as described by the format string.
|
||||
*
|
||||
* @returns SSH_OK on success
|
||||
* SSH_ERROR on error
|
||||
* @warning when using 'P' with a constant size (e.g. 8), do not
|
||||
|
||||
@@ -1738,7 +1738,7 @@ int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len)
|
||||
*/
|
||||
int ssh_channel_is_open(ssh_channel channel)
|
||||
{
|
||||
if (channel == NULL) {
|
||||
if (channel == NULL || channel->session == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
61
src/config.c
61
src/config.c
@@ -544,6 +544,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
&jump_host->username,
|
||||
&jump_host->hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
@@ -566,7 +567,12 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
}
|
||||
} else if (parse_entry) {
|
||||
/* We actually care only about the first item */
|
||||
rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false);
|
||||
rv = ssh_config_parse_uri(cp,
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -582,7 +588,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
|
||||
}
|
||||
} else {
|
||||
/* The rest is just sanity-checked to avoid failures later */
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false);
|
||||
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false, false);
|
||||
if (rv != SSH_OK) {
|
||||
ssh_set_error_invalid(session);
|
||||
goto out;
|
||||
@@ -1040,20 +1046,20 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
break;
|
||||
|
||||
case MATCH_ORIGINALHOST:
|
||||
/* Skip one argument */
|
||||
/* Here we match only one argument */
|
||||
p = ssh_config_get_str_tok(&s, NULL);
|
||||
if (p == NULL || p[0] == '\0') {
|
||||
SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword "
|
||||
"'%s' requires argument", count, p2);
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"line %d: ERROR - Match originalhost keyword "
|
||||
"requires argument",
|
||||
count);
|
||||
SAFE_FREE(x);
|
||||
return -1;
|
||||
}
|
||||
result &=
|
||||
ssh_config_match(session->opts.originalhost, p, negate);
|
||||
args++;
|
||||
SSH_LOG(SSH_LOG_TRACE,
|
||||
"line %d: Unsupported Match keyword '%s', ignoring",
|
||||
count,
|
||||
p2);
|
||||
result = 0;
|
||||
break;
|
||||
|
||||
case MATCH_HOST:
|
||||
@@ -1066,7 +1072,11 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
SAFE_FREE(x);
|
||||
return -1;
|
||||
}
|
||||
result &= ssh_config_match(session->opts.host, p, negate);
|
||||
result &= ssh_config_match(session->opts.host
|
||||
? session->opts.host
|
||||
: session->opts.originalhost,
|
||||
p,
|
||||
negate);
|
||||
args++;
|
||||
break;
|
||||
|
||||
@@ -1154,7 +1164,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
int ok = 0, result = -1;
|
||||
|
||||
*parsing = 0;
|
||||
lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
|
||||
lowerhost = (session->opts.originalhost)
|
||||
? ssh_lowercase(session->opts.originalhost)
|
||||
: NULL;
|
||||
for (p = ssh_config_get_str_tok(&s, NULL);
|
||||
p != NULL && p[0] != '\0';
|
||||
p = ssh_config_get_str_tok(&s, NULL)) {
|
||||
@@ -1181,7 +1193,9 @@ static int ssh_config_parse_line_internal(ssh_session session,
|
||||
if (z == NULL) {
|
||||
z = strdup(p);
|
||||
}
|
||||
session->opts.config_hostname_only = true;
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, z);
|
||||
session->opts.config_hostname_only = false;
|
||||
free(z);
|
||||
}
|
||||
break;
|
||||
@@ -1670,11 +1684,12 @@ int ssh_config_parse_line_cli(ssh_session session, const char *line)
|
||||
true);
|
||||
}
|
||||
|
||||
/* @brief Parse configuration from a file pointer
|
||||
/**
|
||||
* @brief Parse configuration from a file pointer
|
||||
*
|
||||
* @params[in] session The ssh session
|
||||
* @params[in] fp A valid file pointer
|
||||
* @params[in] global Whether the config is global or not
|
||||
* @param[in] session The ssh session
|
||||
* @param[in] fp A valid file pointer
|
||||
* @param[in] global Whether the config is global or not
|
||||
*
|
||||
* @returns 0 on successful parsing the configuration file, -1 on error
|
||||
*/
|
||||
@@ -1696,10 +1711,11 @@ int ssh_config_parse(ssh_session session, FILE *fp, bool global)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @brief Parse configuration file and set the options to the given session
|
||||
/**
|
||||
* @brief Parse configuration file and set the options to the given session
|
||||
*
|
||||
* @params[in] session The ssh session
|
||||
* @params[in] filename The path to the ssh configuration file
|
||||
* @param[in] session The ssh session
|
||||
* @param[in] filename The path to the ssh configuration file
|
||||
*
|
||||
* @returns 0 on successful parsing the configuration file, -1 on error
|
||||
*/
|
||||
@@ -1734,10 +1750,11 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* @brief Parse configuration string and set the options to the given session
|
||||
/**
|
||||
* @brief Parse configuration string and set the options to the given session
|
||||
*
|
||||
* @params[in] session The ssh session
|
||||
* @params[in] input Null terminated string containing the configuration
|
||||
* @param[in] session The ssh session
|
||||
* @param[in] input Null terminated string containing the configuration
|
||||
*
|
||||
* @returns SSH_OK on successful parsing the configuration string,
|
||||
* SSH_ERROR on error
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
149
src/misc.c
149
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)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -106,6 +107,14 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
|
||||
}
|
||||
}
|
||||
|
||||
if (src->opts.originalhost != NULL) {
|
||||
new->opts.originalhost = strdup(src->opts.originalhost);
|
||||
if (new->opts.originalhost == NULL) {
|
||||
ssh_free(new);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (src->opts.bindaddr != NULL) {
|
||||
new->opts.bindaddr = strdup(src->opts.bindaddr);
|
||||
if (new->opts.bindaddr == NULL) {
|
||||
@@ -279,6 +288,20 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @internal
|
||||
* @brief Set a key exchange algorithm list option on the session.
|
||||
*
|
||||
* Supports prefix modifiers: '+' to append, '-' to remove, '^' to prepend
|
||||
* to the default algorithm list.
|
||||
*
|
||||
* @param[in] session The SSH session.
|
||||
* @param[in] algo The algorithm type to configure.
|
||||
* @param[in] list The algorithm list string.
|
||||
* @param[out] place Pointer to the string to store
|
||||
* the resulting algorithm list.
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on error.
|
||||
*/
|
||||
int ssh_options_set_algo(ssh_session session,
|
||||
enum ssh_kex_types_e algo,
|
||||
const char *list,
|
||||
@@ -718,18 +741,52 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
|
||||
return -1;
|
||||
} else {
|
||||
char *username = NULL, *hostname = NULL;
|
||||
rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true);
|
||||
if (rc != SSH_OK) {
|
||||
char *strict_hostname = NULL;
|
||||
|
||||
/* Non-strict parse: reject shell metacharacters */
|
||||
rc = ssh_config_parse_uri(value,
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
if (rc != SSH_OK || hostname == NULL) {
|
||||
SAFE_FREE(username);
|
||||
SAFE_FREE(hostname);
|
||||
ssh_set_error_invalid(session);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Non-strict passed: set username and originalhost */
|
||||
if (username != NULL) {
|
||||
SAFE_FREE(session->opts.username);
|
||||
session->opts.username = username;
|
||||
}
|
||||
if (hostname != NULL) {
|
||||
if (!session->opts.config_hostname_only) {
|
||||
SAFE_FREE(session->opts.originalhost);
|
||||
session->opts.originalhost = hostname;
|
||||
} else {
|
||||
SAFE_FREE(hostname);
|
||||
}
|
||||
|
||||
/* Strict parse: set host only if valid hostname or IP */
|
||||
rc = ssh_config_parse_uri(value,
|
||||
NULL,
|
||||
&strict_hostname,
|
||||
NULL,
|
||||
true,
|
||||
true);
|
||||
if (rc != SSH_OK || strict_hostname == NULL) {
|
||||
SAFE_FREE(session->opts.host);
|
||||
session->opts.host = hostname;
|
||||
SAFE_FREE(strict_hostname);
|
||||
if (session->opts.config_hostname_only) {
|
||||
/* Config path: Hostname must be valid */
|
||||
ssh_set_error_invalid(session);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
SAFE_FREE(session->opts.host);
|
||||
session->opts.host = strict_hostname;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1646,7 +1703,8 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value)
|
||||
switch(type)
|
||||
{
|
||||
case SSH_OPTIONS_HOST:
|
||||
src = session->opts.host;
|
||||
src = session->opts.host ? session->opts.host
|
||||
: session->opts.originalhost;
|
||||
break;
|
||||
|
||||
case SSH_OPTIONS_USER:
|
||||
@@ -1980,7 +2038,7 @@ int ssh_options_parse_config(ssh_session session, const char *filename)
|
||||
if (session == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (session->opts.host == NULL) {
|
||||
if (session->opts.originalhost == NULL) {
|
||||
ssh_set_error_invalid(session);
|
||||
return -1;
|
||||
}
|
||||
@@ -2037,6 +2095,16 @@ out:
|
||||
return r;
|
||||
}
|
||||
|
||||
/** @internal
|
||||
* @brief Apply default values for unset session options.
|
||||
*
|
||||
* Sets default SSH directory and username if not already configured,
|
||||
* and resolves any remaining option expansions.
|
||||
*
|
||||
* @param[in] session The SSH session to apply defaults to.
|
||||
*
|
||||
* @return `SSH_OK` on success, `SSH_ERROR` on error.
|
||||
*/
|
||||
int ssh_options_apply(ssh_session session)
|
||||
{
|
||||
char *tmp = NULL;
|
||||
|
||||
180
src/pki.c
180
src/pki.c
@@ -449,40 +449,137 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) {
|
||||
/* We should never reach this */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum ssh_digest_e ssh_key_hash_from_name(const char *name)
|
||||
/**
|
||||
* @brief Convert a signature algorithm name to a key type and hash type.
|
||||
*
|
||||
* Looks up the given signature algorithm name and returns both the
|
||||
* corresponding key type and digest algorithm in a single call,
|
||||
* avoiding double string comparisons on the same input.
|
||||
*
|
||||
* @param[in] name The signature algorithm name to convert (e.g.
|
||||
* "ssh-rsa", "rsa-sha2-256", "ecdsa-sha2-nistp256").
|
||||
*
|
||||
* @param[out] type A pointer to store the resulting key type.
|
||||
*
|
||||
* @param[out] hash_type A pointer to store the resulting hash/digest type.
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR if name is NULL or
|
||||
* unknown.
|
||||
*/
|
||||
int ssh_key_type_and_hash_from_signature_name(const char *name,
|
||||
enum ssh_keytypes_e *type,
|
||||
enum ssh_digest_e *hash_type)
|
||||
{
|
||||
if (name == NULL) {
|
||||
/* TODO we should rather fail */
|
||||
return SSH_DIGEST_AUTO;
|
||||
size_t len;
|
||||
|
||||
if (name == NULL || type == NULL || hash_type == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
if (strcmp(name, "ssh-rsa") == 0) {
|
||||
return SSH_DIGEST_SHA1;
|
||||
} else if (strcmp(name, "rsa-sha2-256") == 0) {
|
||||
return SSH_DIGEST_SHA256;
|
||||
} else if (strcmp(name, "rsa-sha2-512") == 0) {
|
||||
return SSH_DIGEST_SHA512;
|
||||
} else if (strcmp(name, "ecdsa-sha2-nistp256") == 0) {
|
||||
return SSH_DIGEST_SHA256;
|
||||
} else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) {
|
||||
return SSH_DIGEST_SHA384;
|
||||
} else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) {
|
||||
return SSH_DIGEST_SHA512;
|
||||
} else if (strcmp(name, "ssh-ed25519") == 0) {
|
||||
return SSH_DIGEST_AUTO;
|
||||
} else if (strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) {
|
||||
return SSH_DIGEST_SHA256;
|
||||
} else if (strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) {
|
||||
return SSH_DIGEST_AUTO;
|
||||
len = strlen(name);
|
||||
|
||||
if (len == 7 && strcmp(name, "ssh-rsa") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA;
|
||||
*hash_type = SSH_DIGEST_SHA1;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 11 && strcmp(name, "ssh-ed25519") == 0) {
|
||||
*type = SSH_KEYTYPE_ED25519;
|
||||
*hash_type = SSH_DIGEST_AUTO;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 12) {
|
||||
if (strcmp(name, "rsa-sha2-256") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA;
|
||||
*hash_type = SSH_DIGEST_SHA256;
|
||||
return SSH_OK;
|
||||
}
|
||||
if (strcmp(name, "rsa-sha2-512") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA;
|
||||
*hash_type = SSH_DIGEST_SHA512;
|
||||
return SSH_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 19) {
|
||||
if (strcmp(name, "ecdsa-sha2-nistp256") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P256;
|
||||
*hash_type = SSH_DIGEST_SHA256;
|
||||
return SSH_OK;
|
||||
}
|
||||
if (strcmp(name, "ecdsa-sha2-nistp384") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P384;
|
||||
*hash_type = SSH_DIGEST_SHA384;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (strcmp(name, "ecdsa-sha2-nistp521") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P521;
|
||||
*hash_type = SSH_DIGEST_SHA512;
|
||||
return SSH_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 26 && strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_SK_ED25519;
|
||||
*hash_type = SSH_DIGEST_AUTO;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 28 && strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA1;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 32 && strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_ED25519_CERT01;
|
||||
*hash_type = SSH_DIGEST_AUTO;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 33) {
|
||||
if (strcmp(name, "rsa-sha2-256-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA256;
|
||||
return SSH_OK;
|
||||
}
|
||||
if (strcmp(name, "rsa-sha2-512-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_RSA_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA512;
|
||||
return SSH_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 34 && strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_SK_ECDSA;
|
||||
*hash_type = SSH_DIGEST_SHA256;
|
||||
return SSH_OK;
|
||||
}
|
||||
|
||||
if (len == 40) {
|
||||
if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P256_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA256;
|
||||
return SSH_OK;
|
||||
}
|
||||
if (strcmp(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P384_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA384;
|
||||
return SSH_OK;
|
||||
}
|
||||
if (strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) {
|
||||
*type = SSH_KEYTYPE_ECDSA_P521_CERT01;
|
||||
*hash_type = SSH_DIGEST_SHA512;
|
||||
return SSH_OK;
|
||||
}
|
||||
}
|
||||
|
||||
SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name);
|
||||
|
||||
/* TODO we should rather fail */
|
||||
return SSH_DIGEST_AUTO;
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks the given key against the configured allowed
|
||||
* public key algorithm types
|
||||
@@ -700,27 +797,6 @@ ssh_key_get_signature_algorithm(ssh_session session,
|
||||
return ssh_key_signature_to_char(type, hash_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a ssh key algorithm name to a ssh key algorithm type.
|
||||
*
|
||||
* @param[in] name The name to convert.
|
||||
*
|
||||
* @return The enum ssh key algorithm type.
|
||||
*/
|
||||
enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) {
|
||||
if (name == NULL) {
|
||||
return SSH_KEYTYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
if ((strcmp(name, "rsa-sha2-256") == 0) ||
|
||||
(strcmp(name, "rsa-sha2-512") == 0)) {
|
||||
return SSH_KEYTYPE_RSA;
|
||||
}
|
||||
|
||||
/* Otherwise the key type matches the signature type */
|
||||
return ssh_key_type_from_name(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a ssh key name to a ssh key type.
|
||||
*
|
||||
@@ -2899,8 +2975,12 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob,
|
||||
}
|
||||
|
||||
alg = ssh_string_get_char(algorithm);
|
||||
type = ssh_key_type_from_signature_name(alg);
|
||||
hash_type = ssh_key_hash_from_name(alg);
|
||||
rc = ssh_key_type_and_hash_from_signature_name(alg, &type, &hash_type);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_BUFFER_FREE(buf);
|
||||
SSH_STRING_FREE(algorithm);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
SSH_STRING_FREE(algorithm);
|
||||
|
||||
blob = ssh_buffer_get_ssh_string(buf);
|
||||
|
||||
@@ -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;
|
||||
|
||||
431
src/sftpserver.c
431
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;
|
||||
}
|
||||
@@ -834,14 +837,17 @@ int sftp_reply_version(sftp_client_message client_msg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_buffer_pack(reply, "dssssss",
|
||||
rc = ssh_buffer_pack(reply,
|
||||
"dssssssss",
|
||||
LIBSFTP_VERSION,
|
||||
"posix-rename@openssh.com",
|
||||
"1",
|
||||
"hardlink@openssh.com",
|
||||
"1",
|
||||
"statvfs@openssh.com",
|
||||
"2");
|
||||
"2",
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error_oom(session);
|
||||
SSH_BUFFER_FREE(reply);
|
||||
@@ -948,6 +954,14 @@ void *sftp_handle(sftp_session sftp, ssh_string handle)
|
||||
return sftp->handles[val];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove an SFTP file or directory handle from the session.
|
||||
*
|
||||
* @param sftp The SFTP session.
|
||||
* @param handle The handle to remove.
|
||||
*
|
||||
* @see sftp_handle_alloc()
|
||||
*/
|
||||
void sftp_handle_remove(sftp_session sftp, void *handle)
|
||||
{
|
||||
int i;
|
||||
@@ -1059,6 +1073,352 @@ struct sftp_handle
|
||||
char *name;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Parse a blob of IDs into a sftp_name_id_map.
|
||||
*
|
||||
* This function extracts numeric IDs from the binary blob and populates
|
||||
* the 'ids' array of the map. Note that each element of the 'names'
|
||||
* array in the map is initialized to NULL by this function.
|
||||
*
|
||||
* @param[in] ids_blob The binary string blob containing uint32_t IDs.
|
||||
*
|
||||
* @return A newly allocated sftp_name_id_map on success, or NULL on error.
|
||||
*/
|
||||
static sftp_name_id_map sftp_name_id_map_from_ids_blob(ssh_string ids_blob)
|
||||
{
|
||||
sftp_name_id_map map = NULL;
|
||||
ssh_buffer buf = NULL;
|
||||
size_t len;
|
||||
uint32_t count, i;
|
||||
int rc;
|
||||
|
||||
if (ids_blob == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "IDs blob is NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
len = ssh_string_len(ids_blob);
|
||||
|
||||
if (len % sizeof(uint32_t) != 0) {
|
||||
SSH_LOG(SSH_LOG_WARNING,
|
||||
"IDs blob length is not a multiple of 4 bytes");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
count = len / sizeof(uint32_t);
|
||||
map = sftp_name_id_map_new(count);
|
||||
if (map == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate sftp_name_id_map");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = ssh_buffer_new();
|
||||
if (buf == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate ssh_buffer");
|
||||
sftp_name_id_map_free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = ssh_buffer_add_data(buf, ssh_string_data(ids_blob), len);
|
||||
if (rc < 0) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to copy blob data to buffer");
|
||||
SSH_BUFFER_FREE(buf);
|
||||
sftp_name_id_map_free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
uint32_t val;
|
||||
rc = ssh_buffer_unpack(buf, "d", &val);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to unpack ID from buffer");
|
||||
SSH_BUFFER_FREE(buf);
|
||||
sftp_name_id_map_free(map);
|
||||
return NULL;
|
||||
}
|
||||
map->ids[i] = val;
|
||||
}
|
||||
|
||||
SSH_BUFFER_FREE(buf);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Fill the names array using UIDs in the map.
|
||||
*/
|
||||
static int sftp_fill_names_using_uids(sftp_name_id_map users_map)
|
||||
{
|
||||
struct passwd pwd_struct;
|
||||
struct passwd *pwd_res = NULL;
|
||||
long pwd_buf_size = -1;
|
||||
char *pwd_lookup_buf = NULL;
|
||||
uint32_t i;
|
||||
int rc = SSH_OK;
|
||||
|
||||
if (users_map == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
#ifdef _SC_GETPW_R_SIZE_MAX
|
||||
pwd_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
|
||||
#endif
|
||||
if (pwd_buf_size <= 0) {
|
||||
pwd_buf_size = 16384;
|
||||
}
|
||||
pwd_lookup_buf = calloc(1, pwd_buf_size);
|
||||
if (pwd_lookup_buf == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate pwd lookup buffer");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < users_map->count; i++) {
|
||||
int ret = getpwuid_r(users_map->ids[i],
|
||||
&pwd_struct,
|
||||
pwd_lookup_buf,
|
||||
pwd_buf_size,
|
||||
&pwd_res);
|
||||
|
||||
SAFE_FREE(users_map->names[i]);
|
||||
|
||||
if (ret == 0 && pwd_res != NULL) {
|
||||
users_map->names[i] = strdup(pwd_res->pw_name);
|
||||
} else {
|
||||
users_map->names[i] = strdup("");
|
||||
}
|
||||
|
||||
if (users_map->names[i] == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING,
|
||||
"Failed to allocate memory for username string");
|
||||
rc = SSH_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_FREE(pwd_lookup_buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Fill the names array using GIDs in the map.
|
||||
*/
|
||||
static int sftp_fill_names_using_gids(sftp_name_id_map groups_map)
|
||||
{
|
||||
struct group grp_struct;
|
||||
struct group *grp_res = NULL;
|
||||
long grp_buf_size = -1;
|
||||
char *grp_lookup_buf = NULL;
|
||||
uint32_t i;
|
||||
int rc = SSH_OK;
|
||||
|
||||
if (groups_map == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
#ifdef _SC_GETGR_R_SIZE_MAX
|
||||
grp_buf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
|
||||
#endif
|
||||
if (grp_buf_size <= 0) {
|
||||
grp_buf_size = 16384;
|
||||
}
|
||||
grp_lookup_buf = calloc(1, grp_buf_size);
|
||||
if (grp_lookup_buf == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate grp lookup buffer");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < groups_map->count; i++) {
|
||||
int ret = getgrgid_r(groups_map->ids[i],
|
||||
&grp_struct,
|
||||
grp_lookup_buf,
|
||||
grp_buf_size,
|
||||
&grp_res);
|
||||
|
||||
SAFE_FREE(groups_map->names[i]);
|
||||
|
||||
if (ret == 0 && grp_res != NULL) {
|
||||
groups_map->names[i] = strdup(grp_res->gr_name);
|
||||
} else {
|
||||
groups_map->names[i] = strdup("");
|
||||
}
|
||||
|
||||
if (groups_map->names[i] == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING,
|
||||
"Failed to allocate memory for group name string");
|
||||
rc = SSH_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_FREE(grp_lookup_buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Pack the resolved names from a map into the output buffer.
|
||||
*
|
||||
* This function formats the multiple names according to the
|
||||
* users-groups-by-id@openssh.com extension specification. Each name in
|
||||
* the map is individually encoded as an SSH string. The entire concatenated
|
||||
* sequence of these encoded names is then wrapped and appended to the
|
||||
* output buffer as one single, large SSH string.
|
||||
*
|
||||
* @param[out] out_buffer The destination buffer for the final packed string.
|
||||
* @param[in] map The map containing the resolved names.
|
||||
*
|
||||
* @return SSH_OK on success, or SSH_ERROR on memory allocation failure.
|
||||
*/
|
||||
static int sftp_buffer_add_names(ssh_buffer out_buffer, sftp_name_id_map map)
|
||||
{
|
||||
ssh_buffer temp_buffer = NULL;
|
||||
uint32_t i;
|
||||
int rc;
|
||||
|
||||
if (out_buffer == NULL || map == NULL) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
temp_buffer = ssh_buffer_new();
|
||||
if (temp_buffer == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING,
|
||||
"Failed to allocate temporary buffer for names");
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; i < map->count; i++) {
|
||||
const char *name = map->names[i] != NULL ? map->names[i] : "";
|
||||
rc = ssh_buffer_pack(temp_buffer, "s", name);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to pack name into buffer");
|
||||
SSH_BUFFER_FREE(temp_buffer);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
rc = ssh_buffer_pack(out_buffer,
|
||||
"dP",
|
||||
(uint32_t)ssh_buffer_get_len(temp_buffer),
|
||||
(size_t)ssh_buffer_get_len(temp_buffer),
|
||||
ssh_buffer_get(temp_buffer));
|
||||
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING,
|
||||
"Failed to add names string blob to output buffer");
|
||||
}
|
||||
|
||||
SSH_BUFFER_FREE(temp_buffer);
|
||||
return (rc != SSH_OK) ? SSH_ERROR : SSH_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Handle users-groups-by-id@openssh.com extension request.
|
||||
*
|
||||
* Resolves numeric user IDs (UIDs) and group IDs (GIDs) to their
|
||||
* corresponding username and group name strings. Returns empty strings
|
||||
* for IDs that cannot be resolved.
|
||||
*
|
||||
* @param[in] client_msg The SFTP client message containing the request.
|
||||
* client_msg->data contains the UIDs blob.
|
||||
* client_msg->handle contains the GIDs blob.
|
||||
*
|
||||
* @return SSH_OK on success (reply sent to client). On error, an error
|
||||
* status is sent to the client and SSH_ERROR is returned.
|
||||
*/
|
||||
static int process_users_groups_by_id(sftp_client_message client_msg)
|
||||
{
|
||||
ssh_buffer out = NULL;
|
||||
sftp_name_id_map users_map = NULL;
|
||||
sftp_name_id_map groups_map = NULL;
|
||||
int rc;
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Processing users-groups-by-id extension");
|
||||
|
||||
if (client_msg->data == NULL || client_msg->handle == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Missing UIDs or GIDs blob");
|
||||
goto error;
|
||||
}
|
||||
|
||||
users_map = sftp_name_id_map_from_ids_blob(client_msg->data);
|
||||
if (users_map == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to parse UIDs blob");
|
||||
goto error;
|
||||
}
|
||||
|
||||
groups_map = sftp_name_id_map_from_ids_blob(client_msg->handle);
|
||||
if (groups_map == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to parse GIDs blob");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = sftp_fill_names_using_uids(users_map);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to resolve UIDs");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = sftp_fill_names_using_gids(groups_map);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to resolve GIDs");
|
||||
goto error;
|
||||
}
|
||||
|
||||
out = ssh_buffer_new();
|
||||
if (out == NULL) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to allocate output buffer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = ssh_buffer_add_u32(out, client_msg->id);
|
||||
if (rc < 0) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to add request ID to buffer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = sftp_buffer_add_names(out, users_map);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to add users to buffer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = sftp_buffer_add_names(out, groups_map);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to add groups to buffer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = sftp_packet_write(client_msg->sftp, SSH_FXP_EXTENDED_REPLY, out);
|
||||
if (rc < 0) {
|
||||
SSH_LOG(SSH_LOG_WARNING, "Failed to send extended reply");
|
||||
goto error;
|
||||
}
|
||||
|
||||
sftp_name_id_map_free(users_map);
|
||||
sftp_name_id_map_free(groups_map);
|
||||
SSH_BUFFER_FREE(out);
|
||||
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "Successfully processed request");
|
||||
return SSH_OK;
|
||||
|
||||
error:
|
||||
sftp_name_id_map_free(users_map);
|
||||
sftp_name_id_map_free(groups_map);
|
||||
SSH_BUFFER_FREE(out);
|
||||
SSH_LOG(SSH_LOG_WARNING, "Sending error response");
|
||||
|
||||
sftp_reply_status(client_msg, SSH_FX_FAILURE, "Internal processing error");
|
||||
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
SSH_SFTP_CALLBACK(process_unsupported);
|
||||
SSH_SFTP_CALLBACK(process_open);
|
||||
SSH_SFTP_CALLBACK(process_read);
|
||||
@@ -1103,6 +1463,10 @@ const struct sftp_message_handler message_handlers[] = {
|
||||
const struct sftp_message_handler extended_handlers[] = {
|
||||
/* here are some extended type handlers */
|
||||
{"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs},
|
||||
{"users-groups-by-id",
|
||||
"users-groups-by-id@openssh.com",
|
||||
0,
|
||||
process_users_groups_by_id},
|
||||
{NULL, NULL, 0, NULL},
|
||||
};
|
||||
|
||||
@@ -2071,16 +2435,19 @@ sftp_channel_default_subsystem_request(ssh_session session,
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default data callback for sftp server
|
||||
* @brief Default channel data callback for an SFTP server subsystem.
|
||||
*
|
||||
* @param[in] session The ssh session
|
||||
* @param[in] channel The ssh channel with SFTP session opened
|
||||
* @param[in] data The data to be processed.
|
||||
* @param[in] len The length of input data to be processed
|
||||
* @param[in] is_stderr Unused channel flag for stderr flagging
|
||||
* @param[in] userdata The pointer to sftp_session
|
||||
* Processes incoming data on the channel and dispatches it to the SFTP
|
||||
* server message handler.
|
||||
*
|
||||
* @return number of bytes processed, -1 when error occurs.
|
||||
* @param[in] session The SSH session.
|
||||
* @param[in] channel The SSH channel carrying the SFTP data.
|
||||
* @param[in] data The received data buffer.
|
||||
* @param[in] len The length of the data buffer.
|
||||
* @param[in] is_stderr Unused; SFTP does not use the stderr stream.
|
||||
* @param[in] userdata Pointer to the sftp_session handle.
|
||||
*
|
||||
* @return Number of bytes processed on success, `SSH_ERROR` on error.
|
||||
*/
|
||||
int
|
||||
sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
|
||||
@@ -2093,7 +2460,7 @@ sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
|
||||
sftp_session *sftpp = (sftp_session *)userdata;
|
||||
sftp_session sftp = NULL;
|
||||
sftp_client_message msg;
|
||||
int decode_len;
|
||||
uint32_t undecoded_len = len;
|
||||
int rc;
|
||||
|
||||
if (sftpp == NULL) {
|
||||
@@ -2102,17 +2469,25 @@ sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session),
|
||||
}
|
||||
sftp = *sftpp;
|
||||
|
||||
decode_len = sftp_decode_channel_data_to_packet(sftp, data, len);
|
||||
if (decode_len == SSH_ERROR)
|
||||
return SSH_ERROR;
|
||||
do {
|
||||
int decode_len =
|
||||
sftp_decode_channel_data_to_packet(sftp, data, undecoded_len);
|
||||
if (decode_len == SSH_ERROR) {
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
msg = sftp_get_client_message_from_packet(sftp);
|
||||
rc = process_client_message(msg);
|
||||
sftp_client_message_free(msg);
|
||||
if (rc != SSH_OK)
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!");
|
||||
msg = sftp_get_client_message_from_packet(sftp);
|
||||
rc = process_client_message(msg);
|
||||
sftp_client_message_free(msg);
|
||||
if (rc != SSH_OK) {
|
||||
SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!");
|
||||
}
|
||||
|
||||
return decode_len;
|
||||
undecoded_len -= decode_len;
|
||||
data = (uint8_t *)data + decode_len;
|
||||
} while (undecoded_len > 0);
|
||||
|
||||
return len;
|
||||
}
|
||||
#else
|
||||
/* Not available on Windows for now */
|
||||
|
||||
34
src/socket.c
34
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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -437,6 +437,7 @@ int crypt_set_algorithms_server(ssh_session session){
|
||||
struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab();
|
||||
struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab();
|
||||
int cmp;
|
||||
int rc;
|
||||
|
||||
if (session == NULL) {
|
||||
return SSH_ERROR;
|
||||
@@ -580,8 +581,24 @@ int crypt_set_algorithms_server(ssh_session session){
|
||||
}
|
||||
|
||||
method = session->next_crypto->kex_methods[SSH_HOSTKEYS];
|
||||
session->srv.hostkey = ssh_key_type_from_signature_name(method);
|
||||
session->srv.hostkey_digest = ssh_key_hash_from_name(method);
|
||||
|
||||
/* For GSSAPI key exchange, hostkey algorithm may be "null" */
|
||||
if (strcmp(method, "null") == 0) {
|
||||
session->srv.hostkey = SSH_KEYTYPE_UNKNOWN;
|
||||
session->srv.hostkey_digest = SSH_DIGEST_AUTO;
|
||||
} else {
|
||||
rc = ssh_key_type_and_hash_from_signature_name(
|
||||
method,
|
||||
&session->srv.hostkey,
|
||||
&session->srv.hostkey_digest);
|
||||
if (rc != SSH_OK) {
|
||||
ssh_set_error(session,
|
||||
SSH_FATAL,
|
||||
"unknown hostkey algorithm %s",
|
||||
method);
|
||||
return SSH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* setup DH key exchange type */
|
||||
switch (session->next_crypto->kex_type) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
project(fuzzing CXX)
|
||||
|
||||
# Build SSH server mock helper as object library
|
||||
add_library(ssh_server_mock_obj OBJECT ssh_server_mock.c)
|
||||
target_link_libraries(ssh_server_mock_obj PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
macro(fuzzer name)
|
||||
add_executable(${name} ${name}.c)
|
||||
target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES})
|
||||
|
||||
# Add ssh_server_mock dependency for scp and sftp fuzzers
|
||||
if (${name} STREQUAL "ssh_scp_fuzzer" OR ${name} STREQUAL "ssh_sftp_fuzzer")
|
||||
target_sources(${name} PRIVATE $<TARGET_OBJECTS:ssh_server_mock_obj>)
|
||||
target_link_libraries(${name} PRIVATE pthread)
|
||||
endif()
|
||||
if (WITH_COVERAGE)
|
||||
append_coverage_compiler_flags_to_target(${name})
|
||||
endif (WITH_COVERAGE)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set_target_properties(${name}
|
||||
PROPERTIES
|
||||
@@ -36,6 +50,7 @@ fuzzer(ssh_pubkey_fuzzer)
|
||||
fuzzer(ssh_sftp_attr_fuzzer)
|
||||
fuzzer(ssh_sshsig_fuzzer)
|
||||
if (WITH_SERVER)
|
||||
fuzzer(ssh_scp_fuzzer)
|
||||
fuzzer(ssh_server_fuzzer)
|
||||
fuzzer(ssh_bind_config_fuzzer)
|
||||
endif (WITH_SERVER)
|
||||
|
||||
@@ -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
|
||||
@@ -1,131 +1,133 @@
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/sftp.h"
|
||||
#include "libssh/sftp_priv.h"
|
||||
|
||||
#include "nallocinc.c"
|
||||
|
||||
/* SFTP protocol version constants */
|
||||
#define SFTP_PROTOCOL_VERSION_3 3
|
||||
#define SFTP_PROTOCOL_VERSION_4 4
|
||||
|
||||
/* Flags for sftp_parse_attr expectname parameter */
|
||||
#define SFTP_EXPECT_NAME 1
|
||||
#define SFTP_NO_NAME 0
|
||||
|
||||
/*
|
||||
* Helper to create a minimal sftp_session for fuzzing.
|
||||
* We don't use sftp_new() as it requires a real SSH connection.
|
||||
*/
|
||||
static sftp_session create_minimal_sftp_session(ssh_session session)
|
||||
{
|
||||
sftp_session sftp;
|
||||
|
||||
sftp = calloc(1, sizeof(struct sftp_session_struct));
|
||||
if (sftp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
sftp->session = session;
|
||||
|
||||
return sftp;
|
||||
}
|
||||
|
||||
static void _fuzz_finalize(void)
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
|
||||
nalloc_init(*argv[0]);
|
||||
|
||||
ssh_init();
|
||||
|
||||
atexit(_fuzz_finalize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
ssh_session session = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_buffer buffer = NULL;
|
||||
sftp_attributes attr = NULL;
|
||||
int versions[] = {
|
||||
SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3,
|
||||
SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4
|
||||
};
|
||||
int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME};
|
||||
size_t i;
|
||||
|
||||
/* Minimum bytes for a valid SFTP message */
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(nalloc_start(data, size) > 0);
|
||||
|
||||
/* Allocate shared resources once for all test iterations */
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
sftp = create_minimal_sftp_session(session);
|
||||
if (sftp == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buffer = ssh_buffer_new();
|
||||
if (buffer == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Main fuzzing target: sftp_parse_attr */
|
||||
/* Parses untrusted SFTP messages from client */
|
||||
/* Test all combinations (v3/v4, with/without name) */
|
||||
for (i = 0; i < (sizeof(versions) / sizeof(versions[0])); i++) {
|
||||
sftp->version = versions[i];
|
||||
|
||||
/* Reset and repopulate buffer for each iteration */
|
||||
ssh_buffer_reinit(buffer);
|
||||
if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) {
|
||||
attr = sftp_parse_attr(sftp, buffer, expectnames[i]);
|
||||
sftp_attributes_free(attr);
|
||||
attr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ssh_buffer_free(buffer);
|
||||
free(sftp);
|
||||
ssh_free(session);
|
||||
nalloc_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Copyright 2026 libssh authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LIBSSH_STATIC 1
|
||||
#include "libssh/libssh.h"
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/sftp.h"
|
||||
#include "libssh/sftp_priv.h"
|
||||
|
||||
#include "nallocinc.c"
|
||||
|
||||
/* SFTP protocol version constants */
|
||||
#define SFTP_PROTOCOL_VERSION_3 3
|
||||
#define SFTP_PROTOCOL_VERSION_4 4
|
||||
|
||||
/* Flags for sftp_parse_attr expectname parameter */
|
||||
#define SFTP_EXPECT_NAME 1
|
||||
#define SFTP_NO_NAME 0
|
||||
|
||||
/*
|
||||
* Helper to create a minimal sftp_session for fuzzing.
|
||||
* We don't use sftp_new() as it requires a real SSH connection.
|
||||
*/
|
||||
static sftp_session create_minimal_sftp_session(ssh_session session)
|
||||
{
|
||||
sftp_session sftp;
|
||||
|
||||
sftp = calloc(1, sizeof(struct sftp_session_struct));
|
||||
if (sftp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
sftp->session = session;
|
||||
|
||||
return sftp;
|
||||
}
|
||||
|
||||
static void _fuzz_finalize(void)
|
||||
{
|
||||
ssh_finalize();
|
||||
}
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
|
||||
nalloc_init(*argv[0]);
|
||||
|
||||
ssh_init();
|
||||
|
||||
atexit(_fuzz_finalize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
ssh_session session = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_buffer buffer = NULL;
|
||||
sftp_attributes attr = NULL;
|
||||
int versions[] = {
|
||||
SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3,
|
||||
SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4
|
||||
};
|
||||
int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME};
|
||||
size_t i;
|
||||
|
||||
/* Minimum bytes for a valid SFTP message */
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(nalloc_start(data, size) > 0);
|
||||
|
||||
/* Allocate shared resources once for all test iterations */
|
||||
session = ssh_new();
|
||||
if (session == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
sftp = create_minimal_sftp_session(session);
|
||||
if (sftp == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buffer = ssh_buffer_new();
|
||||
if (buffer == NULL) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Main fuzzing target: sftp_parse_attr */
|
||||
/* Parses untrusted SFTP messages from client */
|
||||
/* Test all combinations (v3/v4, with/without name) */
|
||||
for (i = 0; i < ARRAY_SIZE(versions); i++) {
|
||||
sftp->version = versions[i];
|
||||
|
||||
/* Reset and repopulate buffer for each iteration */
|
||||
ssh_buffer_reinit(buffer);
|
||||
if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) {
|
||||
attr = sftp_parse_attr(sftp, buffer, expectnames[i]);
|
||||
sftp_attributes_free(attr);
|
||||
attr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ssh_buffer_free(buffer);
|
||||
free(sftp);
|
||||
ssh_free(session);
|
||||
nalloc_end();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -849,10 +849,10 @@ static int pkd_run_tests(void) {
|
||||
};
|
||||
|
||||
/* Test list is populated depending on which clients are enabled. */
|
||||
struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) +
|
||||
(sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) +
|
||||
(sizeof(putty_tests) / sizeof(putty_tests[0])) +
|
||||
(sizeof(noop_tests) / sizeof(noop_tests[0]))];
|
||||
struct CMUnitTest all_tests[ARRAY_SIZE(openssh_tests) +
|
||||
ARRAY_SIZE(dropbear_tests) +
|
||||
ARRAY_SIZE(putty_tests) +
|
||||
ARRAY_SIZE(noop_tests)];
|
||||
memset(&all_tests[0], 0x0, sizeof(all_tests));
|
||||
|
||||
/* Generate client keys and populate test list for each enabled client. */
|
||||
@@ -860,10 +860,10 @@ static int pkd_run_tests(void) {
|
||||
setup_openssh_client_keys();
|
||||
if (ssh_fips_mode()) {
|
||||
memcpy(&all_tests[tindex], &openssh_fips_tests[0], sizeof(openssh_fips_tests));
|
||||
tindex += (sizeof(openssh_fips_tests) / sizeof(openssh_fips_tests[0]));
|
||||
tindex += ARRAY_SIZE(openssh_fips_tests);
|
||||
} else {
|
||||
memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests));
|
||||
tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0]));
|
||||
tindex += ARRAY_SIZE(openssh_tests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,7 +871,7 @@ static int pkd_run_tests(void) {
|
||||
setup_dropbear_client_keys();
|
||||
if (!ssh_fips_mode()) {
|
||||
memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests));
|
||||
tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0]));
|
||||
tindex += ARRAY_SIZE(dropbear_tests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,12 +879,12 @@ static int pkd_run_tests(void) {
|
||||
setup_putty_client_keys();
|
||||
if (!ssh_fips_mode()) {
|
||||
memcpy(&all_tests[tindex], &putty_tests[0], sizeof(putty_tests));
|
||||
tindex += (sizeof(putty_tests) / sizeof(putty_tests[0]));
|
||||
tindex += ARRAY_SIZE(putty_tests);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests));
|
||||
tindex += (sizeof(noop_tests) / sizeof(noop_tests[0]));
|
||||
tindex += ARRAY_SIZE(noop_tests);
|
||||
|
||||
if ((pkd_dargs.opts.testname == NULL) &&
|
||||
(pkd_dargs.opts.testmatch == NULL)) {
|
||||
@@ -1158,4 +1158,4 @@ out_finalize:
|
||||
#endif
|
||||
out:
|
||||
return exit_code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,53 @@ struct server_state_st {
|
||||
struct server_state_st *state);
|
||||
};
|
||||
|
||||
/*TODO: Add documentation */
|
||||
/**
|
||||
* @brief Free a server state struct.
|
||||
*
|
||||
* Frees all memory inside server_state_st using SAFE_FREE.
|
||||
*
|
||||
* @param[in] state The server_state_st struct to free.
|
||||
*/
|
||||
void free_server_state(struct server_state_st *state);
|
||||
|
||||
/*TODO: Add documentation */
|
||||
/**
|
||||
* @brief Run a SSH server based on a server state struct.
|
||||
*
|
||||
* Takes a server_state_st struct, validates required fields, and starts
|
||||
* listening for connections. For each client, it forks a child process
|
||||
* that calls handle_session. Blocks until SIGTERM is received.
|
||||
*
|
||||
* @param[in] state The server configuration struct.
|
||||
*
|
||||
* @return SSH_OK on success, SSH_ERROR if an error occurred.
|
||||
*
|
||||
* @note This function blocks until SIGTERM is received.
|
||||
* @note The state is freed internally; do not use after calling.
|
||||
* @note If state->log_file is set, stdout/stderr are redirected to it.
|
||||
*
|
||||
* @see fork_run_server()
|
||||
* @see free_server_state()
|
||||
*/
|
||||
int run_server(struct server_state_st *state);
|
||||
|
||||
/*TODO: Add documentation */
|
||||
/**
|
||||
* @brief Fork and run an SSH server in non-blocking mode.
|
||||
*
|
||||
* Forks a child process that calls run_server(). The parent returns
|
||||
* immediately with the child's PID. Designed for tests that need
|
||||
* a server running in the background.
|
||||
*
|
||||
* @param[in] state The server_state_st struct passed to run_server().
|
||||
* @param[in] free_state Callback to free parent's test data in the child.
|
||||
* @param[in] userdata Pointer passed to free_state.
|
||||
*
|
||||
* @return Child PID on success, -1 on error.
|
||||
*
|
||||
* @note The parent should send SIGTERM to the child PID when done.
|
||||
* @note The state is freed by the child process via run_server().
|
||||
*
|
||||
* @see run_server()
|
||||
*/
|
||||
pid_t
|
||||
fork_run_server(struct server_state_st *state,
|
||||
void (*free_state) (void **userdata),
|
||||
|
||||
@@ -26,10 +26,11 @@
|
||||
|
||||
#define LIBSSH_STATIC
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_VALGRIND_VALGRIND_H
|
||||
#include <valgrind/valgrind.h>
|
||||
@@ -48,6 +49,9 @@
|
||||
|
||||
#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts"
|
||||
|
||||
#define TEST_INVALID_ID_1 99999
|
||||
#define TEST_INVALID_ID_2 88888
|
||||
|
||||
const char template[] = "temp_dir_XXXXXX";
|
||||
|
||||
struct test_server_st {
|
||||
@@ -71,9 +75,9 @@ static void free_test_server_state(void **state)
|
||||
|
||||
static int setup_default_server(void **state)
|
||||
{
|
||||
struct torture_state *s;
|
||||
struct server_state_st *ss;
|
||||
struct test_server_st *tss;
|
||||
struct torture_state *s = NULL;
|
||||
struct server_state_st *ss = NULL;
|
||||
struct test_server_st *tss = NULL;
|
||||
|
||||
char ed25519_hostkey[1024] = {0};
|
||||
char rsa_hostkey[1024];
|
||||
@@ -207,9 +211,9 @@ static int setup_default_server(void **state)
|
||||
|
||||
static int teardown_default_server(void **state)
|
||||
{
|
||||
struct torture_state *s;
|
||||
struct server_state_st *ss;
|
||||
struct test_server_st *tss;
|
||||
struct torture_state *s = NULL;
|
||||
struct server_state_st *ss = NULL;
|
||||
struct test_server_st *tss = NULL;
|
||||
|
||||
tss = *state;
|
||||
assert_non_null(tss);
|
||||
@@ -233,7 +237,7 @@ static int teardown_default_server(void **state)
|
||||
static int session_setup(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_state *s = NULL;
|
||||
int verbosity = torture_libssh_verbosity();
|
||||
char template2[] = "/tmp/ssh_torture_XXXXXX";
|
||||
char *cwd = NULL;
|
||||
@@ -335,7 +339,7 @@ static int session_setup_sftp(void **state)
|
||||
static int session_teardown(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_state *s = NULL;
|
||||
int rc = 0;
|
||||
|
||||
assert_non_null(tss);
|
||||
@@ -365,10 +369,10 @@ static int session_teardown(void **state)
|
||||
static void torture_server_establish_sftp(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
ssh_session session;
|
||||
sftp_session sftp;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
@@ -418,17 +422,17 @@ static void torture_server_establish_sftp(void **state)
|
||||
static void torture_server_test_sftp_function(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
ssh_session session;
|
||||
sftp_session sftp;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
int rc;
|
||||
char *rv_str;
|
||||
sftp_dir dir;
|
||||
char *rv_str = NULL;
|
||||
sftp_dir dir = NULL;
|
||||
|
||||
char data[65535] = {0};
|
||||
sftp_file source;
|
||||
sftp_file to;
|
||||
sftp_file source = NULL;
|
||||
sftp_file to = NULL;
|
||||
int read_len;
|
||||
int write_len;
|
||||
|
||||
@@ -675,10 +679,10 @@ static void torture_server_sftp_open_read_write(void **state)
|
||||
static void torture_server_sftp_mkdir(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
sftp_session sftp;
|
||||
ssh_session session;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
sftp_file new_file = NULL;
|
||||
char tmp_dir[PATH_MAX] = {0};
|
||||
char tmp_file[PATH_MAX] = {0};
|
||||
@@ -750,10 +754,10 @@ static void torture_server_sftp_mkdir(void **state)
|
||||
static void torture_server_sftp_realpath(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
sftp_session sftp;
|
||||
ssh_session session;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
char path[PATH_MAX] = {0};
|
||||
char exp_path[PATH_MAX] = {0};
|
||||
char *new_path = NULL;
|
||||
@@ -799,10 +803,10 @@ static void torture_server_sftp_realpath(void **state)
|
||||
static void torture_server_sftp_symlink(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
sftp_session sftp;
|
||||
ssh_session session;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
sftp_file new_file = NULL;
|
||||
char tmp_dir[PATH_MAX] = {0};
|
||||
char tmp_file[PATH_MAX] = {0};
|
||||
@@ -811,7 +815,7 @@ static void torture_server_sftp_symlink(void **state)
|
||||
char data[42] = "012345678901234567890123456789012345678901";
|
||||
char *new_path = NULL;
|
||||
sftp_attributes a = NULL;
|
||||
sftp_dir dir;
|
||||
sftp_dir dir = NULL;
|
||||
int write_len, num_files = 0;
|
||||
int rc;
|
||||
|
||||
@@ -946,10 +950,10 @@ static void torture_server_sftp_symlink(void **state)
|
||||
static void torture_server_sftp_extended(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s;
|
||||
struct torture_sftp *tsftp;
|
||||
sftp_session sftp;
|
||||
ssh_session session;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_session sftp = NULL;
|
||||
ssh_session session = NULL;
|
||||
sftp_file new_file = NULL;
|
||||
char tmp_dir[PATH_MAX] = {0};
|
||||
char tmp_file[PATH_MAX] = {0};
|
||||
@@ -1095,7 +1099,7 @@ static void torture_server_sftp_handles_exhaustion(void **state)
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
char name[128] = {0};
|
||||
sftp_file handle, handles[SFTP_HANDLES] = {0};
|
||||
sftp_file handle = NULL, handles[SFTP_HANDLES] = {0};
|
||||
sftp_session sftp = NULL;
|
||||
int rc;
|
||||
|
||||
@@ -1300,6 +1304,274 @@ static void torture_server_sftp_payload_overrun(void **state)
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_basic(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_name_id_map users_map = NULL;
|
||||
sftp_name_id_map groups_map = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
users_map = sftp_name_id_map_new(2);
|
||||
assert_non_null(users_map);
|
||||
|
||||
groups_map = sftp_name_id_map_new(2);
|
||||
assert_non_null(groups_map);
|
||||
|
||||
users_map->ids[0] = TORTURE_SSH_USER_ID_BOB;
|
||||
users_map->ids[1] = TORTURE_SSH_USER_ID_ALICE;
|
||||
|
||||
groups_map->ids[0] = TORTURE_SSH_GROUP_ID_ROOT;
|
||||
groups_map->ids[1] = TORTURE_SSH_GROUP_ID_COMMON;
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
assert_non_null(users_map->names[0]);
|
||||
assert_string_equal(users_map->names[0], TORTURE_SSH_USER_BOB);
|
||||
|
||||
assert_non_null(users_map->names[1]);
|
||||
assert_string_equal(users_map->names[1], TORTURE_SSH_USER_ALICE);
|
||||
|
||||
assert_non_null(groups_map->names[0]);
|
||||
assert_string_equal(groups_map->names[0], TORTURE_SSH_GROUP_ROOT);
|
||||
|
||||
assert_non_null(groups_map->names[1]);
|
||||
assert_string_equal(groups_map->names[1], TORTURE_SSH_GROUP_COMMON);
|
||||
|
||||
sftp_name_id_map_free(users_map);
|
||||
sftp_name_id_map_free(groups_map);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_unknown(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_name_id_map users_map = NULL;
|
||||
sftp_name_id_map groups_map = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
users_map = sftp_name_id_map_new(1);
|
||||
assert_non_null(users_map);
|
||||
|
||||
groups_map = sftp_name_id_map_new(1);
|
||||
assert_non_null(groups_map);
|
||||
|
||||
/* Set User ID and Group ID that do not exist */
|
||||
users_map->ids[0] = TEST_INVALID_ID_1;
|
||||
groups_map->ids[0] = TEST_INVALID_ID_1;
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
assert_non_null(users_map->names[0]);
|
||||
assert_string_equal(users_map->names[0], "");
|
||||
|
||||
assert_non_null(groups_map->names[0]);
|
||||
assert_string_equal(groups_map->names[0], "");
|
||||
|
||||
sftp_name_id_map_free(users_map);
|
||||
sftp_name_id_map_free(groups_map);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_users_only(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_name_id_map users_map = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
users_map = sftp_name_id_map_new(1);
|
||||
assert_non_null(users_map);
|
||||
|
||||
users_map->ids[0] = TORTURE_SSH_USER_ID_ALICE;
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, NULL);
|
||||
assert_int_equal(rc, 0);
|
||||
assert_non_null(users_map->names[0]);
|
||||
assert_string_equal(users_map->names[0], TORTURE_SSH_USER_ALICE);
|
||||
|
||||
sftp_name_id_map_free(users_map);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_groups_only(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_name_id_map groups_map = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
groups_map = sftp_name_id_map_new(1);
|
||||
assert_non_null(groups_map);
|
||||
|
||||
groups_map->ids[0] = TORTURE_SSH_GROUP_ID_ROOT;
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, NULL, groups_map);
|
||||
assert_int_equal(rc, 0);
|
||||
assert_non_null(groups_map->names[0]);
|
||||
assert_string_equal(groups_map->names[0], TORTURE_SSH_GROUP_ROOT);
|
||||
|
||||
sftp_name_id_map_free(groups_map);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_multiple(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
sftp_name_id_map users_map = NULL;
|
||||
sftp_name_id_map groups_map = NULL;
|
||||
uint32_t i = 0;
|
||||
int rc;
|
||||
|
||||
struct {
|
||||
uid_t id;
|
||||
const char *name;
|
||||
} expected_users[] = {
|
||||
{TORTURE_SSH_USER_ID_BOB, TORTURE_SSH_USER_BOB},
|
||||
{TEST_INVALID_ID_1, ""},
|
||||
{TORTURE_SSH_USER_ID_ALICE, TORTURE_SSH_USER_ALICE},
|
||||
{TEST_INVALID_ID_2, ""},
|
||||
{TORTURE_SSH_USER_ID_CHARLIE, TORTURE_SSH_USER_CHARLIE}};
|
||||
size_t num_expected_users = ARRAY_SIZE(expected_users);
|
||||
|
||||
struct {
|
||||
gid_t id;
|
||||
const char *name;
|
||||
} expected_groups[] = {
|
||||
{TORTURE_SSH_GROUP_ID_ROOT, TORTURE_SSH_GROUP_ROOT},
|
||||
{TEST_INVALID_ID_1, ""},
|
||||
{TORTURE_SSH_GROUP_ID_COMMON, TORTURE_SSH_GROUP_COMMON},
|
||||
{TEST_INVALID_ID_2, ""},
|
||||
{TORTURE_SSH_GROUP_ID_SSHD, TORTURE_SSH_GROUP_SSHD}};
|
||||
size_t num_expected_groups = ARRAY_SIZE(expected_groups);
|
||||
|
||||
assert_non_null(tss);
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
users_map = sftp_name_id_map_new(num_expected_users);
|
||||
assert_non_null(users_map);
|
||||
|
||||
groups_map = sftp_name_id_map_new(num_expected_groups);
|
||||
assert_non_null(groups_map);
|
||||
|
||||
for (i = 0; i < num_expected_users; i++) {
|
||||
users_map->ids[i] = expected_users[i].id;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_expected_groups; i++) {
|
||||
groups_map->ids[i] = expected_groups[i].id;
|
||||
}
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, users_map, groups_map);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
for (i = 0; i < num_expected_users; i++) {
|
||||
assert_non_null(users_map->names[i]);
|
||||
assert_string_equal(users_map->names[i], expected_users[i].name);
|
||||
}
|
||||
|
||||
for (i = 0; i < num_expected_groups; i++) {
|
||||
assert_non_null(groups_map->names[i]);
|
||||
assert_string_equal(groups_map->names[i], expected_groups[i].name);
|
||||
}
|
||||
|
||||
sftp_name_id_map_free(users_map);
|
||||
sftp_name_id_map_free(groups_map);
|
||||
}
|
||||
|
||||
static void torture_sftp_users_groups_by_id_negative(void **state)
|
||||
{
|
||||
struct test_server_st *tss = *state;
|
||||
struct torture_state *s = NULL;
|
||||
struct torture_sftp *tsftp = NULL;
|
||||
int rc;
|
||||
|
||||
assert_non_null(tss);
|
||||
|
||||
s = tss->state;
|
||||
assert_non_null(s);
|
||||
|
||||
tsftp = s->ssh.tsftp;
|
||||
assert_non_null(tsftp);
|
||||
|
||||
rc = sftp_extension_supported(tsftp->sftp,
|
||||
"users-groups-by-id@openssh.com",
|
||||
"1");
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
rc = sftp_get_users_groups_by_id(NULL, NULL, NULL);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
rc = sftp_get_users_groups_by_id(tsftp->sftp, NULL, NULL);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
int torture_run_tests(void) {
|
||||
int rc;
|
||||
struct CMUnitTest tests[] = {
|
||||
@@ -1330,15 +1602,38 @@ int torture_run_tests(void) {
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_opendir_handles_exhaustion,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_server_sftp_opendir_handles_exhaustion,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_server_sftp_payload_overrun,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_users_groups_by_id_basic,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_sftp_users_groups_by_id_unknown,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_sftp_users_groups_by_id_users_only,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_sftp_users_groups_by_id_groups_only,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_sftp_users_groups_by_id_multiple,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
torture_sftp_users_groups_by_id_negative,
|
||||
session_setup_sftp,
|
||||
session_teardown),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
@@ -53,6 +53,18 @@
|
||||
#define TORTURE_SSH_USER_CHARLIE "charlie"
|
||||
#define TORTURE_SSH_USER_NONEUSER "noneuser"
|
||||
|
||||
#define TORTURE_SSH_GROUP_ROOT "root"
|
||||
#define TORTURE_SSH_GROUP_COMMON "users"
|
||||
#define TORTURE_SSH_GROUP_SSHD "sshd"
|
||||
|
||||
#define TORTURE_SSH_USER_ID_BOB 5000
|
||||
#define TORTURE_SSH_USER_ID_ALICE 5001
|
||||
#define TORTURE_SSH_USER_ID_CHARLIE 5002
|
||||
|
||||
#define TORTURE_SSH_GROUP_ID_ROOT 0
|
||||
#define TORTURE_SSH_GROUP_ID_COMMON 9000
|
||||
#define TORTURE_SSH_GROUP_ID_SSHD 65531
|
||||
|
||||
/* Used by main to communicate with parse_opt. */
|
||||
struct argument_s {
|
||||
const char *pattern;
|
||||
@@ -131,7 +143,7 @@ void torture_sftp_close(struct torture_sftp *t);
|
||||
void torture_write_file(const char *filename, const char *data);
|
||||
|
||||
#define torture_filter_tests(tests) \
|
||||
_torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0])
|
||||
_torture_filter_tests(tests, ARRAY_SIZE(tests))
|
||||
void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests);
|
||||
|
||||
const char *torture_server_address(int domain);
|
||||
|
||||
@@ -24,8 +24,12 @@
|
||||
#ifndef _TORTURE_KEY_H
|
||||
#define _TORTURE_KEY_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "libssh/libssh.h"
|
||||
|
||||
#define TORTURE_TESTKEY_PASSWORD "libssh-rocks"
|
||||
|
||||
/* Return the encrypted private key in a new OpenSSH format */
|
||||
|
||||
@@ -10,20 +10,22 @@
|
||||
|
||||
#define LIMIT (8*1024*1024)
|
||||
|
||||
static int setup(void **state) {
|
||||
ssh_buffer buffer;
|
||||
static int setup(void **state)
|
||||
{
|
||||
ssh_buffer buffer = NULL;
|
||||
|
||||
buffer = ssh_buffer_new();
|
||||
if (buffer == NULL) {
|
||||
return -1;
|
||||
}
|
||||
ssh_buffer_set_secure(buffer);
|
||||
*state = (void *) buffer;
|
||||
*state = (void *)buffer;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int teardown(void **state) {
|
||||
static int teardown(void **state)
|
||||
{
|
||||
SSH_BUFFER_FREE(*state);
|
||||
|
||||
return 0;
|
||||
@@ -33,158 +35,196 @@ static int teardown(void **state) {
|
||||
* Test if the continuously growing buffer size never exceeds 2 time its
|
||||
* real capacity
|
||||
*/
|
||||
static void torture_growing_buffer(void **state) {
|
||||
ssh_buffer buffer = *state;
|
||||
int i;
|
||||
static void torture_growing_buffer(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
int i;
|
||||
|
||||
for(i=0;i<LIMIT;++i){
|
||||
ssh_buffer_add_data(buffer,"A",1);
|
||||
if(buffer->used >= 128){
|
||||
if(ssh_buffer_get_len(buffer) * 2 < buffer->allocated){
|
||||
assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated);
|
||||
}
|
||||
for (i = 0; i < LIMIT; ++i) {
|
||||
ssh_buffer_add_data(buffer, "A", 1);
|
||||
if (buffer->used >= 128) {
|
||||
if (ssh_buffer_get_len(buffer) * 2 < buffer->allocated) {
|
||||
assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if the continuously growing buffer size never exceeds 2 time its
|
||||
* real capacity, when we remove 1 byte after each call (sliding window)
|
||||
*/
|
||||
static void torture_growing_buffer_shifting(void **state) {
|
||||
ssh_buffer buffer = *state;
|
||||
int i;
|
||||
unsigned char c;
|
||||
for(i=0; i<1024;++i){
|
||||
ssh_buffer_add_data(buffer,"S",1);
|
||||
}
|
||||
for(i=0;i<LIMIT;++i){
|
||||
ssh_buffer_get_u8(buffer,&c);
|
||||
ssh_buffer_add_data(buffer,"A",1);
|
||||
if(buffer->used >= 128){
|
||||
if(ssh_buffer_get_len(buffer) * 4 < buffer->allocated){
|
||||
assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated);
|
||||
return;
|
||||
}
|
||||
static void torture_growing_buffer_shifting(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
int i;
|
||||
unsigned char c;
|
||||
|
||||
for (i = 0; i < 1024; ++i) {
|
||||
ssh_buffer_add_data(buffer, "S", 1);
|
||||
}
|
||||
for (i = 0; i < LIMIT; ++i) {
|
||||
ssh_buffer_get_u8(buffer, &c);
|
||||
ssh_buffer_add_data(buffer, "A", 1);
|
||||
if (buffer->used >= 128) {
|
||||
if (ssh_buffer_get_len(buffer) * 4 < buffer->allocated) {
|
||||
assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the behavior of ssh_buffer_prepend_data
|
||||
*/
|
||||
static void torture_buffer_prepend(void **state) {
|
||||
ssh_buffer buffer = *state;
|
||||
uint32_t v;
|
||||
ssh_buffer_add_data(buffer,"abcdef",6);
|
||||
ssh_buffer_prepend_data(buffer,"xyz",3);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer),9);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9);
|
||||
static void torture_buffer_prepend(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
uint32_t v;
|
||||
|
||||
/* Now remove 4 bytes and see if we can replace them */
|
||||
ssh_buffer_get_u32(buffer,&v);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer),5);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
|
||||
ssh_buffer_add_data(buffer, "abcdef", 6);
|
||||
ssh_buffer_prepend_data(buffer, "xyz", 3);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer), 9);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9);
|
||||
|
||||
ssh_buffer_prepend_data(buffer,"aris",4);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer),9);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9);
|
||||
/* Now remove 4 bytes and see if we can replace them */
|
||||
ssh_buffer_get_u32(buffer, &v);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer), 5);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
|
||||
|
||||
/* same thing but we add 5 bytes now */
|
||||
ssh_buffer_get_u32(buffer,&v);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer),5);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
|
||||
ssh_buffer_prepend_data(buffer, "aris", 4);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer), 9);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9);
|
||||
|
||||
ssh_buffer_prepend_data(buffer,"12345",5);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer),10);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10);
|
||||
/* same thing but we add 5 bytes now */
|
||||
ssh_buffer_get_u32(buffer, &v);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer), 5);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5);
|
||||
|
||||
ssh_buffer_prepend_data(buffer, "12345", 5);
|
||||
assert_int_equal(ssh_buffer_get_len(buffer), 10);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the behavior of ssh_buffer_get_ssh_string with invalid data
|
||||
*/
|
||||
static void torture_ssh_buffer_get_ssh_string(void **state) {
|
||||
ssh_buffer buffer;
|
||||
int i,j,k,l, rc;
|
||||
/* some values that can go wrong */
|
||||
uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00,
|
||||
0x80000000, 0x80000004, 0x7fffffff};
|
||||
char data[128];
|
||||
(void)state;
|
||||
memset(data,'X',sizeof(data));
|
||||
for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){
|
||||
for(j=0; j< (int)sizeof(data);++j){
|
||||
for(k=1;k<5;++k){
|
||||
buffer = ssh_buffer_new();
|
||||
assert_non_null(buffer);
|
||||
static void torture_ssh_buffer_get_ssh_string(void **state)
|
||||
{
|
||||
ssh_buffer buffer = NULL;
|
||||
int i, j, k, l, rc;
|
||||
/* some values that can go wrong */
|
||||
uint32_t values[] = {0xffffffff,
|
||||
0xfffffffe,
|
||||
0xfffffffc,
|
||||
0xffffff00,
|
||||
0x80000000,
|
||||
0x80000004,
|
||||
0x7fffffff};
|
||||
char data[128];
|
||||
|
||||
for(l=0;l<k;++l){
|
||||
rc = ssh_buffer_add_u32(buffer,htonl(values[i]));
|
||||
assert_int_equal(rc, 0);
|
||||
(void)state;
|
||||
|
||||
memset(data, 'X', sizeof(data));
|
||||
for (i = 0; i < (int)ARRAY_SIZE(values); ++i) {
|
||||
for (j = 0; j < (int)sizeof(data); ++j) {
|
||||
for (k = 1; k < 5; ++k) {
|
||||
buffer = ssh_buffer_new();
|
||||
assert_non_null(buffer);
|
||||
|
||||
for (l = 0; l < k; ++l) {
|
||||
rc = ssh_buffer_add_u32(buffer, htonl(values[i]));
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
rc = ssh_buffer_add_data(buffer, data, j);
|
||||
assert_int_equal(rc, 0);
|
||||
for (l = 0; l < k; ++l) {
|
||||
ssh_string str = ssh_buffer_get_ssh_string(buffer);
|
||||
assert_null(str);
|
||||
SSH_STRING_FREE(str);
|
||||
}
|
||||
SSH_BUFFER_FREE(buffer);
|
||||
}
|
||||
}
|
||||
rc = ssh_buffer_add_data(buffer,data,j);
|
||||
assert_int_equal(rc, 0);
|
||||
for(l=0;l<k;++l){
|
||||
ssh_string str = ssh_buffer_get_ssh_string(buffer);
|
||||
assert_null(str);
|
||||
SSH_STRING_FREE(str);
|
||||
}
|
||||
SSH_BUFFER_FREE(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void torture_ssh_buffer_add_format(void **state) {
|
||||
ssh_buffer buffer=*state;
|
||||
static void torture_ssh_buffer_add_format(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
uint8_t b;
|
||||
uint16_t w;
|
||||
uint32_t d;
|
||||
uint64_t q;
|
||||
ssh_string s;
|
||||
ssh_string s = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06" "libssh"
|
||||
"\x00\x00\x00\x05" "rocks"
|
||||
"So much"
|
||||
"Fun!";
|
||||
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06"
|
||||
"libssh"
|
||||
"\x00\x00\x00\x05"
|
||||
"rocks"
|
||||
"So much"
|
||||
"Fun!";
|
||||
|
||||
b=0x42;
|
||||
w=0x1337;
|
||||
d=0xbadc0de;
|
||||
q=0x13243546acbdcedf;
|
||||
s=ssh_string_from_char("libssh");
|
||||
rc=ssh_buffer_pack(buffer, "bwdqSsPt",b,w,d,q,s,"rocks",(size_t)7,"So much","Fun!");
|
||||
b = 0x42;
|
||||
w = 0x1337;
|
||||
d = 0xbadc0de;
|
||||
q = 0x13243546acbdcedf;
|
||||
s = ssh_string_from_char("libssh");
|
||||
rc = ssh_buffer_pack(buffer,
|
||||
"bwdqSsPt",
|
||||
b,
|
||||
w,
|
||||
d,
|
||||
q,
|
||||
s,
|
||||
"rocks",
|
||||
(size_t)7,
|
||||
"So much",
|
||||
"Fun!");
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
len = ssh_buffer_get_len(buffer);
|
||||
assert_int_equal(len, sizeof(verif) - 1);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1);
|
||||
assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) - 1);
|
||||
|
||||
SSH_STRING_FREE(s);
|
||||
}
|
||||
|
||||
static void torture_ssh_buffer_get_format(void **state) {
|
||||
ssh_buffer buffer=*state;
|
||||
uint8_t b=0;
|
||||
uint16_t w=0;
|
||||
uint32_t d=0;
|
||||
uint64_t q=0;
|
||||
ssh_string s=NULL;
|
||||
char *s1=NULL, *s2=NULL;
|
||||
static void torture_ssh_buffer_get_format(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
uint8_t b = 0;
|
||||
uint16_t w = 0;
|
||||
uint32_t d = 0;
|
||||
uint64_t q = 0;
|
||||
ssh_string s = NULL;
|
||||
char *s1 = NULL, *s2 = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06" "libssh"
|
||||
"\x00\x00\x00\x05" "rocks"
|
||||
"So much";
|
||||
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06"
|
||||
"libssh"
|
||||
"\x00\x00\x00\x05"
|
||||
"rocks"
|
||||
"So much";
|
||||
|
||||
rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
rc = ssh_buffer_unpack(buffer, "bwdqSsP",&b,&w,&d,&q,&s,&s1,(size_t)7,&s2);
|
||||
rc = ssh_buffer_unpack(buffer,
|
||||
"bwdqSsP",
|
||||
&b,
|
||||
&w,
|
||||
&d,
|
||||
&q,
|
||||
&s,
|
||||
&s1,
|
||||
(size_t)7,
|
||||
&s2);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
assert_int_equal(b, 0x42);
|
||||
@@ -210,24 +250,37 @@ static void torture_ssh_buffer_get_format(void **state) {
|
||||
SAFE_FREE(s2);
|
||||
}
|
||||
|
||||
static void torture_ssh_buffer_get_format_error(void **state) {
|
||||
ssh_buffer buffer=*state;
|
||||
uint8_t b=0;
|
||||
uint16_t w=0;
|
||||
uint32_t d=0;
|
||||
uint64_t q=0;
|
||||
ssh_string s=NULL;
|
||||
char *s1=NULL, *s2=NULL;
|
||||
static void torture_ssh_buffer_get_format_error(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
uint8_t b = 0;
|
||||
uint16_t w = 0;
|
||||
uint32_t d = 0;
|
||||
uint64_t q = 0;
|
||||
ssh_string s = NULL;
|
||||
char *s1 = NULL, *s2 = NULL;
|
||||
int rc;
|
||||
uint8_t verif[]="\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06" "libssh"
|
||||
"\x00\x00\x00\x05" "rocks"
|
||||
"So much";
|
||||
uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46"
|
||||
"\xac\xbd\xce\xdf"
|
||||
"\x00\x00\x00\x06"
|
||||
"libssh"
|
||||
"\x00\x00\x00\x05"
|
||||
"rocks"
|
||||
"So much";
|
||||
|
||||
rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
rc = ssh_buffer_unpack(buffer, "bwdqSsPb",&b,&w,&d,&q,&s,&s1,(size_t)7,&s2,&b);
|
||||
rc = ssh_buffer_unpack(buffer,
|
||||
"bwdqSsPb",
|
||||
&b,
|
||||
&w,
|
||||
&d,
|
||||
&q,
|
||||
&s,
|
||||
&s1,
|
||||
(size_t)7,
|
||||
&s2,
|
||||
&b);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
assert_null(s);
|
||||
@@ -235,7 +288,8 @@ static void torture_ssh_buffer_get_format_error(void **state) {
|
||||
assert_null(s2);
|
||||
}
|
||||
|
||||
static void torture_buffer_pack_badformat(void **state){
|
||||
static void torture_buffer_pack_badformat(void **state)
|
||||
{
|
||||
ssh_buffer buffer = *state;
|
||||
uint8_t b = 42;
|
||||
int rc;
|
||||
|
||||
@@ -39,10 +39,29 @@ static void torture_channel_select(void **state)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void torture_channel_null_session(void **state)
|
||||
{
|
||||
ssh_channel channel = NULL;
|
||||
|
||||
(void)state;
|
||||
|
||||
channel = calloc(1, sizeof(struct ssh_channel_struct));
|
||||
|
||||
assert_non_null(channel);
|
||||
|
||||
channel->state = SSH_CHANNEL_STATE_OPEN;
|
||||
channel->session = NULL;
|
||||
|
||||
assert_int_equal(ssh_channel_is_open(channel), 0);
|
||||
|
||||
free(channel);
|
||||
}
|
||||
|
||||
int torture_run_tests(void) {
|
||||
int rc;
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(torture_channel_select),
|
||||
cmocka_unit_test(torture_channel_null_session),
|
||||
};
|
||||
|
||||
ssh_init();
|
||||
|
||||
@@ -130,23 +130,23 @@ extern LIBSSH_THREAD int ssh_log_level;
|
||||
"ProxyJump = many-spaces.com\n" /* valid */
|
||||
|
||||
/* Match keyword */
|
||||
#define LIBSSH_TESTCONFIG_STRING10 \
|
||||
"Match host example\n" \
|
||||
"\tHostName example.com\n" \
|
||||
"Match host example1,example2\n" \
|
||||
"\tHostName exampleN\n" \
|
||||
"Match user guest\n" \
|
||||
"\tHostName guest.com\n" \
|
||||
"Match user tester host testhost\n" \
|
||||
"\tHostName testhost.com\n" \
|
||||
#define LIBSSH_TESTCONFIG_STRING10 \
|
||||
"Match host example\n" \
|
||||
"\tHostName example.com\n" \
|
||||
"Match host example1,example2\n" \
|
||||
"\tHostName exampleN\n" \
|
||||
"Match user guest\n" \
|
||||
"\tHostName guest.com\n" \
|
||||
"Match user tester host testhost\n" \
|
||||
"\tHostName testhost.com\n" \
|
||||
"Match !user tester host testhost\n" \
|
||||
"\tHostName nonuser-testhost.com\n" \
|
||||
"Match all\n" \
|
||||
"\tHostName all-matched.com\n" \
|
||||
/* Unsupported options */ \
|
||||
"Match originalhost example\n" \
|
||||
"\tHostName original-example.com\n" \
|
||||
"Match localuser guest\n" \
|
||||
"\tHostName nonuser-testhost.com\n" \
|
||||
"Match all\n" \
|
||||
"\tHostName all-matched.com\n" \
|
||||
"Match originalhost example\n" \
|
||||
"\tHostName original-example.com\n" \
|
||||
"\tUser originaluser\n" \
|
||||
"Match localuser guest\n" \
|
||||
"\tHostName local-guest.com\n"
|
||||
|
||||
/* ProxyJump */
|
||||
@@ -851,26 +851,40 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "all-matched.com");
|
||||
assert_string_equal(session->opts.originalhost, "unmatched");
|
||||
|
||||
/* Hostname example does simple hostname matching */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "example.com");
|
||||
assert_string_equal(session->opts.originalhost, "example");
|
||||
|
||||
/* We can match also both hosts from a comma separated list */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example1");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "exampleN");
|
||||
assert_string_equal(session->opts.originalhost, "example1");
|
||||
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example2");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "exampleN");
|
||||
assert_string_equal(session->opts.originalhost, "example2");
|
||||
|
||||
/* We can match by user */
|
||||
/* We can match by originalhost */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "example");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "example.com");
|
||||
assert_string_equal(session->opts.originalhost, "example");
|
||||
/* Match originalhost sets User */
|
||||
assert_string_equal(session->opts.username, "originaluser");
|
||||
|
||||
/* We can match by user - clear originalhost to isolate user match */
|
||||
torture_reset_config(session);
|
||||
SAFE_FREE(session->opts.originalhost);
|
||||
ssh_options_set(session, SSH_OPTIONS_USER, "guest");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "guest.com");
|
||||
@@ -881,6 +895,7 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "testhost.com");
|
||||
assert_string_equal(session->opts.originalhost, "testhost");
|
||||
|
||||
/* We can also negate conditions */
|
||||
torture_reset_config(session);
|
||||
@@ -888,9 +903,43 @@ static void torture_config_match(void **state,
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "testhost");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "nonuser-testhost.com");
|
||||
assert_string_equal(session->opts.originalhost, "testhost");
|
||||
|
||||
/* In this part, we try various other config files and strings. */
|
||||
|
||||
/* Match host compares against resolved hostname */
|
||||
config = "Host ssh-host\n"
|
||||
"\tHostname 10.1.1.1\n"
|
||||
"Match host 10.1.1.*\n"
|
||||
"\tPort 2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
session->opts.port = 0;
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "10.1.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "ssh-host");
|
||||
assert_int_equal(session->opts.port, 2222);
|
||||
|
||||
/* Match host falls back to originalhost when host is NULL */
|
||||
config = "Match host my_alias\n"
|
||||
"\tHostName alias-matched.com\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
SAFE_FREE(session->opts.username);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "alias-matched.com");
|
||||
|
||||
/* Match final is not completely supported, but should do quite much the
|
||||
* same as "match all". The trailing "all" is not mandatory. */
|
||||
config = "Match final all\n"
|
||||
@@ -1018,7 +1067,7 @@ static void torture_config_match(void **state,
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.host, "unmatched");
|
||||
|
||||
/* Missing argument to unsupported option originalhost */
|
||||
/* Missing argument to option originalhost */
|
||||
config = "Match originalhost\n"
|
||||
"\tHost originalhost.com\n";
|
||||
if (file != NULL) {
|
||||
@@ -1289,7 +1338,6 @@ static void torture_config_proxyjump(void **state,
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -W '[%h]:%p' 2620:52:0::fed");
|
||||
|
||||
|
||||
/* Multiple @ is allowed in second jump */
|
||||
config = "Host allowed-hostname\n"
|
||||
"\tProxyJump localhost,user@principal.com@jumpbox:22\n";
|
||||
@@ -1351,7 +1399,73 @@ static void torture_config_proxyjump(void **state,
|
||||
"jumpbox",
|
||||
"user@principal.com",
|
||||
"22");
|
||||
/* Non-RFC-1035 alias (underscore) — accepted with non-strict parse */
|
||||
config = "Host alias-jump\n"
|
||||
"\tProxyJump my_alias\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root,
|
||||
"my_alias",
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
/* Non-RFC-1035 alias in multi-hop second jump */
|
||||
config = "Host alias-multi\n"
|
||||
"\tProxyJump localhost,my_alias:2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root,
|
||||
"my_alias",
|
||||
NULL,
|
||||
"2222");
|
||||
helper_proxy_jump_check(session->opts.proxy_jumps->root->next,
|
||||
"localhost",
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
/* Non-RFC-1035 alias — proxycommand based */
|
||||
torture_setenv("OPENSSH_PROXYJUMP", "1");
|
||||
|
||||
config = "Host alias-jump\n"
|
||||
"\tProxyJump my_alias\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-jump");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -W '[%h]:%p' my_alias");
|
||||
|
||||
/* Non-RFC-1035 alias in multi-hop — proxycommand based */
|
||||
config = "Host alias-multi\n"
|
||||
"\tProxyJump localhost,my_alias:2222\n";
|
||||
if (file != NULL) {
|
||||
torture_write_file(file, config);
|
||||
} else {
|
||||
string = config;
|
||||
}
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "alias-multi");
|
||||
_parse_config(session, file, string, SSH_OK);
|
||||
assert_string_equal(session->opts.ProxyCommand,
|
||||
"ssh -J my_alias:2222 -W '[%h]:%p' localhost");
|
||||
|
||||
torture_unsetenv("OPENSSH_PROXYJUMP");
|
||||
|
||||
/* In this part, we try various other config files and strings. */
|
||||
torture_setenv("OPENSSH_PROXYJUMP", "1");
|
||||
@@ -2762,21 +2876,36 @@ static void torture_config_parse_uri(void **state)
|
||||
|
||||
(void)state; /* unused */
|
||||
|
||||
rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("localhost",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "localhost");
|
||||
SAFE_FREE(hostname);
|
||||
assert_null(port);
|
||||
|
||||
rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("1.2.3.4",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1.2.3.4");
|
||||
SAFE_FREE(hostname);
|
||||
assert_null(port);
|
||||
|
||||
rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("1.2.3.4:2222",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1.2.3.4");
|
||||
@@ -2784,7 +2913,12 @@ static void torture_config_parse_uri(void **state)
|
||||
assert_string_equal(port, "2222");
|
||||
SAFE_FREE(port);
|
||||
|
||||
rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false);
|
||||
rc = ssh_config_parse_uri("[1:2:3::4]:2222",
|
||||
&username,
|
||||
&hostname,
|
||||
&port,
|
||||
false,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1:2:3::4");
|
||||
@@ -2793,13 +2927,64 @@ static void torture_config_parse_uri(void **state)
|
||||
SAFE_FREE(port);
|
||||
|
||||
/* do not want port */
|
||||
rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true);
|
||||
rc = ssh_config_parse_uri("1:2:3::4",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
true);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "1:2:3::4");
|
||||
SAFE_FREE(hostname);
|
||||
|
||||
rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true);
|
||||
rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true, true);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict accepts non-RFC1035 chars (e.g. _, %) */
|
||||
rc = ssh_config_parse_uri("customer_1",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(username);
|
||||
assert_string_equal(hostname, "customer_1");
|
||||
SAFE_FREE(hostname);
|
||||
|
||||
rc = ssh_config_parse_uri("admin@%prod",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_return_code(rc, errno);
|
||||
assert_string_equal(username, "admin");
|
||||
assert_string_equal(hostname, "%prod");
|
||||
SAFE_FREE(username);
|
||||
SAFE_FREE(hostname);
|
||||
|
||||
/* Strict rejects what non-strict accepts */
|
||||
rc = ssh_config_parse_uri("customer_1",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
true);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict rejects shell metacharacters */
|
||||
rc = ssh_config_parse_uri("host;cmd",
|
||||
&username,
|
||||
&hostname,
|
||||
NULL,
|
||||
true,
|
||||
false);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Non-strict rejects leading dash */
|
||||
rc = ssh_config_parse_uri("-host", &username, &hostname, NULL, true, false);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
@@ -2933,6 +3118,48 @@ static void torture_config_jump(void **state)
|
||||
printf("%s: EOF\n", __func__);
|
||||
}
|
||||
|
||||
/* Verify Hostname directive resolves host without overwriting originalhost
|
||||
*/
|
||||
static void torture_config_hostname(void **state)
|
||||
{
|
||||
ssh_session session = *state;
|
||||
char *expanded = NULL;
|
||||
|
||||
/* Hostname directive sets host, originalhost is unchanged */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
_parse_config(session,
|
||||
NULL,
|
||||
"Host my_alias\n\tHostname 192.168.1.1\n",
|
||||
SSH_OK);
|
||||
assert_string_equal(session->opts.host, "192.168.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
|
||||
/* Host keyword compares against originalhost, not the resolved IP */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "ssh-host");
|
||||
_parse_config(session,
|
||||
NULL,
|
||||
"Host ssh-host\n\tHostname 10.1.1.1\n"
|
||||
"Host 10.1.1.*\n\tProxyJump ssh-host\n",
|
||||
SSH_OK);
|
||||
assert_string_equal(session->opts.host, "10.1.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "ssh-host");
|
||||
assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0);
|
||||
assert_null(session->opts.ProxyCommand);
|
||||
|
||||
/* %h falls back to originalhost when host is not yet resolved */
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
expanded = ssh_path_expand_escape(session, "%h");
|
||||
assert_non_null(expanded);
|
||||
assert_string_equal(expanded, "my_alias");
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
/* Invalid configuration files
|
||||
*/
|
||||
static void torture_config_invalid(void **state)
|
||||
@@ -3101,7 +3328,8 @@ int torture_run_tests(void)
|
||||
cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value,
|
||||
setup,
|
||||
teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_jump,
|
||||
cmocka_unit_test_setup_teardown(torture_config_jump, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_hostname,
|
||||
setup,
|
||||
teardown),
|
||||
cmocka_unit_test_setup_teardown(torture_config_invalid,
|
||||
|
||||
@@ -584,7 +584,7 @@ torture_match_cidr_address_list_ipv6(void **state)
|
||||
(void)state;
|
||||
|
||||
/* Test valid link-local addresses */
|
||||
valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]);
|
||||
valid_addr_len = ARRAY_SIZE(valid_addr);
|
||||
for (i = 0; i < valid_addr_len; i++) {
|
||||
rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6);
|
||||
assert_int_equal(rc, 1);
|
||||
@@ -601,7 +601,7 @@ torture_match_cidr_address_list_ipv6(void **state)
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
/* Test some invalid input */
|
||||
invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]);
|
||||
invalid_addr_len = ARRAY_SIZE(invalid_addr);
|
||||
for (i = 0; i < invalid_addr_len; i++) {
|
||||
rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
#include <libssh/pki.h>
|
||||
#include <libssh/pki_priv.h>
|
||||
#include <libssh/session.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <netioapi.h>
|
||||
#else
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SERVER
|
||||
#include <libssh/bind.h>
|
||||
#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config"
|
||||
@@ -59,12 +66,16 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "localhost");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "localhost");
|
||||
|
||||
/* IPv4 address */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1");
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "127.1.1.1");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "127.1.1.1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IPv6 address */
|
||||
@@ -72,12 +83,16 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "::1");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "::1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation");
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "meditation");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "meditation");
|
||||
assert_non_null(session->opts.username);
|
||||
assert_string_equal(session->opts.username, "guru");
|
||||
|
||||
@@ -86,6 +101,8 @@ static void torture_options_set_host(void **state) {
|
||||
assert_true(rc == 0);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "hostname");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "hostname");
|
||||
assert_non_null(session->opts.username);
|
||||
assert_string_equal(session->opts.username, "at@login");
|
||||
|
||||
@@ -104,6 +121,9 @@ static void torture_options_set_host(void **state) {
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IPv6 hostnames should work also with square braces */
|
||||
@@ -114,20 +134,103 @@ static void torture_options_set_host(void **state) {
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost,
|
||||
"fd4d:5449:7400:111:626d:3cff:fedf:4d39");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* user@IPv6%interface
|
||||
* Use dynamic interface name for cross-platform portability */
|
||||
{
|
||||
char interf[IF_NAMESIZE] = {0};
|
||||
char ipv6_zone[128] = {0};
|
||||
char expected_host[128] = {0};
|
||||
|
||||
if_indextoname(1, interf);
|
||||
assert_non_null(interf);
|
||||
snprintf(ipv6_zone, sizeof(ipv6_zone), "user@fe80::1%%%s", interf);
|
||||
snprintf(expected_host, sizeof(expected_host), "fe80::1%%%s", interf);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, ipv6_zone);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, expected_host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, expected_host);
|
||||
assert_string_equal(session->opts.username, "user");
|
||||
}
|
||||
|
||||
/* IDN need to be in punycode format */
|
||||
SAFE_FREE(session->opts.username);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld");
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(session->opts.host);
|
||||
assert_string_equal(session->opts.host, "xn--bcher-kva.tld");
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "xn--bcher-kva.tld");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* IDN in UTF8 won't work */
|
||||
/* IDN in UTF-8 is accepted but not as a valid hostname,
|
||||
* only originalhost is set */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld");
|
||||
assert_string_equal(ssh_get_error(session),
|
||||
"Invalid argument in ssh_options_set");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "bücher.tld");
|
||||
|
||||
/* Config alias '%' rejected by RFC1035, only originalhost is set */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "%customer1");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "%customer1");
|
||||
|
||||
/* user@alias '_' rejected by RFC1035, alias stored in originalhost */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "admin@customer_1");
|
||||
assert_return_code(rc, errno);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_string_equal(session->opts.username, "admin");
|
||||
|
||||
/* Shell metacharacters and leading dash rejected.
|
||||
* Verify failure cases do not update session options. */
|
||||
SAFE_FREE(session->opts.username);
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "host;rm -rf /");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "-leading-dash");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
/* Empty user or host rejected */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "@hostname");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "user@");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "");
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
assert_string_equal(session->opts.originalhost, "customer_1");
|
||||
assert_null(session->opts.username);
|
||||
}
|
||||
|
||||
static void torture_options_set_ciphers(void **state)
|
||||
@@ -714,6 +817,26 @@ static void torture_options_get_host(void **state)
|
||||
|
||||
assert_string_equal(host, "localhost");
|
||||
ssh_string_free_char(host);
|
||||
|
||||
/* When host is not yet resolved, falls back to originalhost */
|
||||
rc = ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_true(rc == 0);
|
||||
assert_null(session->opts.host);
|
||||
assert_non_null(session->opts.originalhost);
|
||||
|
||||
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
|
||||
assert_string_equal(host, "my_alias");
|
||||
ssh_string_free_char(host);
|
||||
|
||||
/* After config resolution, get returns resolved host, not originalhost */
|
||||
session->opts.host = strdup("192.168.1.1");
|
||||
assert_non_null(session->opts.host);
|
||||
|
||||
assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host));
|
||||
assert_string_equal(host, "192.168.1.1");
|
||||
ssh_string_free_char(host);
|
||||
/* originalhost is unchanged */
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
}
|
||||
|
||||
static void torture_options_set_port(void **state)
|
||||
@@ -1074,6 +1197,7 @@ static void torture_options_config_host(void **state)
|
||||
{
|
||||
ssh_session session = *state;
|
||||
FILE *config = NULL;
|
||||
int rv;
|
||||
|
||||
/* create a new config file */
|
||||
config = fopen("test_config", "w");
|
||||
@@ -1113,6 +1237,33 @@ static void torture_options_config_host(void **state)
|
||||
ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(session->opts.port, 44);
|
||||
|
||||
/* ssh_options_parse_config rejects when originalhost is NULL */
|
||||
SAFE_FREE(session->opts.host);
|
||||
SAFE_FREE(session->opts.originalhost);
|
||||
rv = ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(rv, -1);
|
||||
|
||||
/* Config Hostname with invalid hostname: verify stale host not leaked */
|
||||
torture_write_file("test_config", "Host 192.168.1.1\nHostname my_alias\n");
|
||||
|
||||
torture_reset_config(session);
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "192.168.1.1");
|
||||
assert_string_equal(session->opts.host, "192.168.1.1");
|
||||
assert_string_equal(session->opts.originalhost, "192.168.1.1");
|
||||
rv = ssh_options_parse_config(session, "test_config");
|
||||
assert_int_equal(rv, 0);
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "192.168.1.1");
|
||||
|
||||
/* Calling ssh_options_set(HOST) twice: verify stale host not leaked */
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "real.server.com");
|
||||
assert_string_equal(session->opts.host, "real.server.com");
|
||||
assert_string_equal(session->opts.originalhost, "real.server.com");
|
||||
|
||||
ssh_options_set(session, SSH_OPTIONS_HOST, "my_alias");
|
||||
assert_null(session->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, "my_alias");
|
||||
|
||||
unlink("test_config");
|
||||
}
|
||||
|
||||
@@ -1437,6 +1588,7 @@ static void torture_options_copy(void **state)
|
||||
|
||||
assert_string_equal(session->opts.username, new->opts.username);
|
||||
assert_string_equal(session->opts.host, new->opts.host);
|
||||
assert_string_equal(session->opts.originalhost, new->opts.originalhost);
|
||||
assert_string_equal(session->opts.bindaddr, new->opts.bindaddr);
|
||||
assert_string_equal(session->opts.sshdir, new->opts.sshdir);
|
||||
assert_string_equal(session->opts.knownhosts, new->opts.knownhosts);
|
||||
|
||||
@@ -107,6 +107,76 @@ static void torture_pki_signature(void **state)
|
||||
ssh_signature_free(sig);
|
||||
}
|
||||
|
||||
static void torture_pki_type_and_hash_from_signature_name(void **state)
|
||||
{
|
||||
enum ssh_keytypes_e type;
|
||||
enum ssh_digest_e hash_type;
|
||||
int rc;
|
||||
|
||||
(void)state; /* unused */
|
||||
|
||||
/* Test NULL input */
|
||||
rc = ssh_key_type_and_hash_from_signature_name(NULL, &type, &hash_type);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Test NULL output pointers */
|
||||
rc = ssh_key_type_and_hash_from_signature_name("ssh-rsa", NULL, &hash_type);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
rc = ssh_key_type_and_hash_from_signature_name("ssh-rsa", &type, NULL);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Test unknown name */
|
||||
rc = ssh_key_type_and_hash_from_signature_name("unknown-algo",
|
||||
&type,
|
||||
&hash_type);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
/* Test valid RSA signatures */
|
||||
rc =
|
||||
ssh_key_type_and_hash_from_signature_name("ssh-rsa", &type, &hash_type);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_int_equal(type, SSH_KEYTYPE_RSA);
|
||||
assert_int_equal(hash_type, SSH_DIGEST_SHA1);
|
||||
|
||||
rc = ssh_key_type_and_hash_from_signature_name("rsa-sha2-256",
|
||||
&type,
|
||||
&hash_type);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_int_equal(type, SSH_KEYTYPE_RSA);
|
||||
assert_int_equal(hash_type, SSH_DIGEST_SHA256);
|
||||
|
||||
rc = ssh_key_type_and_hash_from_signature_name("rsa-sha2-512",
|
||||
&type,
|
||||
&hash_type);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_int_equal(type, SSH_KEYTYPE_RSA);
|
||||
assert_int_equal(hash_type, SSH_DIGEST_SHA512);
|
||||
|
||||
/* Test valid ECDSA signatures */
|
||||
rc = ssh_key_type_and_hash_from_signature_name("ecdsa-sha2-nistp256",
|
||||
&type,
|
||||
&hash_type);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256);
|
||||
assert_int_equal(hash_type, SSH_DIGEST_SHA256);
|
||||
|
||||
/* Test valid ED25519 signature */
|
||||
rc = ssh_key_type_and_hash_from_signature_name("ssh-ed25519",
|
||||
&type,
|
||||
&hash_type);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_int_equal(type, SSH_KEYTYPE_ED25519);
|
||||
assert_int_equal(hash_type, SSH_DIGEST_AUTO);
|
||||
|
||||
/* Test that loose key names are rejected */
|
||||
rc = ssh_key_type_and_hash_from_signature_name("ecdsa", &type, &hash_type);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
|
||||
rc = ssh_key_type_and_hash_from_signature_name("rsa", &type, &hash_type);
|
||||
assert_int_equal(rc, SSH_ERROR);
|
||||
}
|
||||
|
||||
struct key_attrs {
|
||||
int sign;
|
||||
int verify;
|
||||
@@ -415,6 +485,7 @@ int torture_run_tests(void) {
|
||||
struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(torture_pki_keytype),
|
||||
cmocka_unit_test(torture_pki_signature),
|
||||
cmocka_unit_test(torture_pki_type_and_hash_from_signature_name),
|
||||
cmocka_unit_test_setup_teardown(torture_pki_verify_mismatch,
|
||||
setup_cert_dir,
|
||||
teardown_cert_dir),
|
||||
|
||||
@@ -647,7 +647,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
|
||||
ssh_session session=ssh_new();
|
||||
(void) state;
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key);
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P256, NULL, &key);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(key);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
@@ -690,7 +690,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key);
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P384, NULL, &key);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(key);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
@@ -733,7 +733,7 @@ static void torture_pki_generate_key_ecdsa(void **state)
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key);
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_ECDSA_P521, NULL, &key);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(key);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
|
||||
@@ -546,7 +546,7 @@ static void torture_pki_ed25519_generate_key(void **state)
|
||||
|
||||
assert_non_null(session);
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 256, &key);
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_ED25519, NULL, &key);
|
||||
assert_true(rc == SSH_OK);
|
||||
assert_non_null(key);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
|
||||
@@ -440,7 +440,7 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state)
|
||||
SSH_KEY_FREE(pubkey);
|
||||
|
||||
/* Generate different key and try to assign it this certificate */
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &privkey);
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &privkey);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(privkey);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey);
|
||||
@@ -651,8 +651,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
|
||||
ssh_signature_free(sign);
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
key = NULL;
|
||||
pubkey = NULL;
|
||||
}
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
|
||||
@@ -668,8 +666,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
|
||||
ssh_signature_free(sign);
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
key = NULL;
|
||||
pubkey = NULL;
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key);
|
||||
assert_return_code(rc, errno);
|
||||
@@ -684,8 +680,6 @@ static void torture_pki_generate_rsa_deprecated(void **state)
|
||||
ssh_signature_free(sign);
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
key = NULL;
|
||||
pubkey = NULL;
|
||||
|
||||
ssh_free(session);
|
||||
}
|
||||
@@ -760,15 +754,33 @@ static void torture_pki_rsa_sha2(void **state)
|
||||
|
||||
static void torture_pki_rsa_key_size(void **state)
|
||||
{
|
||||
int rc;
|
||||
int rc, bit_size;
|
||||
ssh_key key = NULL, pubkey = NULL;
|
||||
ssh_signature sign = NULL;
|
||||
ssh_session session=ssh_new();
|
||||
unsigned int length = 4096;
|
||||
ssh_pki_ctx ctx = NULL;
|
||||
|
||||
(void) state;
|
||||
(void)state;
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
|
||||
ctx = ssh_pki_ctx_new();
|
||||
assert_non_null(ctx);
|
||||
|
||||
/* Invalid argument NULL */
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, NULL);
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
|
||||
/* Too small size */
|
||||
bit_size = 768;
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
|
||||
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
|
||||
|
||||
/* Ok value */
|
||||
bit_size = 2048;
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_return_code(rc, errno);
|
||||
assert_non_null(key);
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
@@ -790,9 +802,7 @@ static void torture_pki_rsa_key_size(void **state)
|
||||
ssh_signature_free(sign);
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
key = NULL;
|
||||
pubkey = NULL;
|
||||
|
||||
SSH_PKI_CTX_FREE(ctx);
|
||||
ssh_free(session);
|
||||
}
|
||||
|
||||
@@ -890,11 +900,19 @@ static void torture_pki_sign_data_rsa(void **state)
|
||||
{
|
||||
int rc;
|
||||
ssh_key key = NULL;
|
||||
ssh_pki_ctx ctx = NULL;
|
||||
int bit_size = 2048;
|
||||
|
||||
(void) state;
|
||||
|
||||
/* Setup */
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
|
||||
ctx = ssh_pki_ctx_new();
|
||||
assert_non_null(ctx);
|
||||
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(key);
|
||||
|
||||
@@ -914,6 +932,7 @@ static void torture_pki_sign_data_rsa(void **state)
|
||||
|
||||
/* Cleanup */
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_PKI_CTX_FREE(ctx);
|
||||
}
|
||||
|
||||
static void torture_pki_fail_sign_with_incompatible_hash(void **state)
|
||||
@@ -921,12 +940,20 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state)
|
||||
int rc;
|
||||
ssh_key key = NULL;
|
||||
ssh_key pubkey = NULL;
|
||||
ssh_pki_ctx ctx = NULL;
|
||||
int bit_size = 2048;
|
||||
ssh_signature sig, bad_sig;
|
||||
|
||||
(void) state;
|
||||
|
||||
/* Setup */
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
|
||||
ctx = ssh_pki_ctx_new();
|
||||
assert_non_null(ctx);
|
||||
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &bit_size);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_int_equal(rc, SSH_OK);
|
||||
assert_non_null(key);
|
||||
|
||||
@@ -956,6 +983,7 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state)
|
||||
ssh_signature_free(sig);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_PKI_CTX_FREE(ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -142,8 +142,7 @@ static int setup_sshsig_compat(void **state)
|
||||
test_state->original_cwd = original_cwd;
|
||||
test_state->temp_dir = temp_dir;
|
||||
test_state->test_combinations = test_combinations;
|
||||
test_state->num_combinations =
|
||||
sizeof(test_combinations) / sizeof(test_combinations[0]);
|
||||
test_state->num_combinations = ARRAY_SIZE(test_combinations);
|
||||
|
||||
*state = test_state;
|
||||
|
||||
|
||||
@@ -550,14 +550,23 @@ static void *thread_pki_rsa_generate_key(void *threadid)
|
||||
ssh_key key = NULL, pubkey = NULL;
|
||||
ssh_signature sign = NULL;
|
||||
ssh_session session = NULL;
|
||||
ssh_pki_ctx ctx = NULL;
|
||||
int size = 0;
|
||||
|
||||
(void) threadid;
|
||||
|
||||
session = ssh_new();
|
||||
assert_non_null(session);
|
||||
|
||||
ctx = ssh_pki_ctx_new();
|
||||
assert_non_null(ctx);
|
||||
|
||||
if (!ssh_fips_mode()) {
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key);
|
||||
size = 1024;
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_ssh_return_code(session, rc);
|
||||
assert_non_null(key);
|
||||
|
||||
@@ -576,7 +585,11 @@ static void *thread_pki_rsa_generate_key(void *threadid)
|
||||
SSH_KEY_FREE(pubkey);
|
||||
}
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key);
|
||||
size = 2048;
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_ssh_return_code(session, rc);
|
||||
assert_non_null(key);
|
||||
|
||||
@@ -594,8 +607,12 @@ static void *thread_pki_rsa_generate_key(void *threadid)
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
|
||||
rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key);
|
||||
assert_true(rc == SSH_OK);
|
||||
size = 4096;
|
||||
rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &size);
|
||||
assert_return_code(rc, errno);
|
||||
|
||||
rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key);
|
||||
assert_ssh_return_code(session, rc);
|
||||
assert_non_null(key);
|
||||
|
||||
rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey);
|
||||
@@ -612,6 +629,7 @@ static void *thread_pki_rsa_generate_key(void *threadid)
|
||||
SSH_KEY_FREE(key);
|
||||
SSH_KEY_FREE(pubkey);
|
||||
|
||||
SSH_PKI_CTX_FREE(ctx);
|
||||
ssh_free(session);
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -421,3 +421,95 @@
|
||||
fun:gss_acquire_cred_from
|
||||
fun:gss_acquire_cred
|
||||
}
|
||||
|
||||
# Function mecherror_copy called in various
|
||||
# functions of the krb5 library copies entries
|
||||
# to the global error mapping table (mecherrmap m).
|
||||
{
|
||||
Global error mapping table in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
fun:mecherror_copy
|
||||
}
|
||||
|
||||
# Function add_error_table called in various
|
||||
# functions of the krb5 library adds entries
|
||||
# to a global list of error tables et_list.
|
||||
{
|
||||
Global list of error tables in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
fun:add_error_table
|
||||
}
|
||||
|
||||
# Function build_mechSet builds the global
|
||||
# gss_OID_set_desc g_mechSet which is only
|
||||
# free'd when initialized again.
|
||||
{
|
||||
Global OID set in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:build_mechSet
|
||||
}
|
||||
|
||||
# Function gssint_register_mechinfo()
|
||||
# called from gssint_mechglue_init() adds
|
||||
# entries to a global linked list g_mechList.
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:gssint_register_mechinfo*
|
||||
...
|
||||
fun:gssint_mechglue_init
|
||||
}
|
||||
|
||||
# Function addConfigEntry() called during
|
||||
# updateMechList() adds entries to
|
||||
# a global linked list g_mechList.
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:addConfigEntry
|
||||
...
|
||||
fun:updateMechList
|
||||
}
|
||||
|
||||
# Function loadInterMech() called during
|
||||
# updateMechList() loops through the global
|
||||
# linked list g_mechList and updates its entries
|
||||
# with heap-alloced "interposer fields".
|
||||
{
|
||||
Global list of gss_mech_info in krb5
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:loadInterMech
|
||||
...
|
||||
fun:updateMechList
|
||||
}
|
||||
|
||||
# Multiple krb5 functions call krb5int_open_plugin
|
||||
# which opens shared libraries using dlopen.
|
||||
# The plugin handle then seems to be stored in the
|
||||
# main krb5 context.
|
||||
{
|
||||
Plugin handles stored in the krb5 context
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: reachable
|
||||
fun:malloc
|
||||
...
|
||||
fun:dlopen*
|
||||
...
|
||||
fun:krb5int_open_plugin
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user