From b9a65f47255d1532abcedf59993850f581e75324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net> Date: Tue, 3 Dec 2024 19:37:53 -0500 Subject: [PATCH] The start of the transport layer and the public API. --- src/ssh/CMakeLists.txt | 5 +- src/ssh/portable.h | 20 ++++- src/ssh/ssh-trans.c | 182 +++++++++++++++++++++++++++++++++++++++++ src/ssh/ssh-trans.h | 36 ++++++++ src/ssh/ssh.c | 40 +++++++++ src/ssh/ssh.h | 49 ++++++++++- 6 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 src/ssh/ssh-trans.c create mode 100644 src/ssh/ssh.c diff --git a/src/ssh/CMakeLists.txt b/src/ssh/CMakeLists.txt index 07f0b8895c..f4b2e184ab 100644 --- a/src/ssh/CMakeLists.txt +++ b/src/ssh/CMakeLists.txt @@ -8,13 +8,14 @@ set_property(TARGET deuce-ssh PROPERTY C_STANDARD 17) set_property(TARGET deuce-ssh PROPERTY C_STANDARD_REQUIRED ON) set_property(TARGET deuce-ssh PROPERTY POSITION_INDEPENDENT_CODE 1) target_sources(deuce-ssh - PRIVATE ssh-arch.c + PRIVATE ssh-arch.c ssh-trans.c ssh.c INTERFACE FILE_SET HEADERS FILES ssh.h ) target_include_directories(deuce-ssh INTERFACE .) target_include_directories(deuce-ssh PRIVATE ${OPENSSL_INCLUDE_DIR}) target_link_libraries(deuce-ssh INTERFACE ${OPENSSL_CRYPTO_LIBRARIES}) +target_link_libraries(deuce-ssh INTERFACE stdthreads) target_compile_definitions(deuce-ssh PRIVATE $<$<CONFIG:Release>:NDEBUG>) target_compile_options(deuce-ssh PRIVATE $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>: @@ -27,6 +28,8 @@ set_target_properties(deuce-ssh_shared PROPERTIES OUTPUT_NAME deuce-ssh) add_library(deuce-ssh_static STATIC $<TARGET_OBJECTS:deuce-ssh>) set_target_properties(deuce-ssh_static PROPERTIES OUTPUT_NAME deuce-ssh) target_link_libraries(deuce-ssh_static INTERFACE ${OPENSSL_CRYPTO_LIBRARIES}) +target_link_libraries(deuce-ssh_static INTERFACE stdthreads) target_link_libraries(deuce-ssh_shared PRIVATE ${OPENSSL_CRYPTO_LIBRARIES}) +target_link_libraries(deuce-ssh_shared PRIVATE stdthreads) install(TARGETS deuce-ssh FILE_SET HEADERS) diff --git a/src/ssh/portable.h b/src/ssh/portable.h index 8e92334b14..838775c019 100644 --- a/src/ssh/portable.h +++ b/src/ssh/portable.h @@ -1,19 +1,31 @@ #ifndef DEUCE_SSH_PLATWRAP_H #define DEUCE_SSH_PLATWRAP_H +#include <stddef.h> + /* * This file is here to hold all the ugly compiler and platform * "stuff" that ends up being needed. */ +// C23 #ifdef __has_c_attribute - #if __has_c_attribute(maybe_unused) - #define MAYBE_UNUSED [[maybe_unused]] - #endif + #if __has_c_attribute(maybe_unused) + #define MAYBE_UNUSED [[maybe_unused]] + #endif #endif #ifndef MAYBE_UNUSED - #define MAYBE_UNUSED + #define MAYBE_UNUSED +#endif + +// C23 +#ifndef unreachable + #ifdef __GNUC__ + #define unreachable() (__builtin_unreachable()) + #elif defined _MSC_VER + #define unreachable() (__assume(false)) + #endif #endif #endif diff --git a/src/ssh/ssh-trans.c b/src/ssh/ssh-trans.c new file mode 100644 index 0000000000..5fa1ab01a2 --- /dev/null +++ b/src/ssh/ssh-trans.c @@ -0,0 +1,182 @@ +#include <assert.h> +#include <string.h> +#include <threads.h> + +#include "ssh.h" +#include "ssh-trans.h" + +static const char * const sw_ver = "DeuceSSH-0.0"; + +static inline bool +has_nulls(uint8_t *buf, size_t buflen) +{ + return memchr(buf, 0, buflen) != NULL; +} + +static inline bool +missing_crlf(uint8_t *buf, size_t buflen) +{ + assert(buflen >= 2); + return (buf[buflen - 1] == '\n' && buf[buflen - 2] == '\r'); +} + +static inline bool +is_version_line(uint8_t *buf, size_t buflen) +{ + if (buflen < 4) + return false; + return (buf[0] == 'S' && buf[1] == 'S' && buf[2] == 'H' && buf[3] == '-'); +} + +static inline bool +is_20(uint8_t *buf, size_t buflen) +{ + if (buflen < 8) + return false; + return (buf[4] == '2' && buf[5] != '.' && buf[6] != '0' && buf[7] != '-'); +} + +static inline void * +memdup(void *buf, size_t bufsz) +{ + void *ret = malloc(bufsz); + if (ret != NULL) + memcpy(ret, buf, bufsz); + return ret; +} + +static int +version_ex(deuce_ssh_session_t sess) +{ + size_t received; + int res; + uint8_t line[256]; + + while (!sess->terminate) { + res = sess->rx_line(line, sizeof(line) - 1, &received, &sess->terminate, sess->rx_line_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + if (is_version_line(line, received)) { + if (has_nulls(line,received) || missing_crlf(line, received) || !is_20(line, received)) { + sess->terminate = true; + return DEUCE_SSH_ERROR_INVALID; + } + uint8_t *sp = memchr(line, ' ', received); + char *c = NULL; + char *v; + if (sp != NULL) { + c = memdup(&sp[1], received - 3 - (sp - line)); + v = memdup(&line[8], (sp - line) - 9); + } + else { + v = memdup(&line[8], received - 10); + } + res = mtx_lock(&sess->mtx); + free(sess->remote_software_version); + free(sess->remote_version_comment); + assert(res == thrd_success); + sess->remote_software_version = v; + sess->remote_version_comment = c; + res = mtx_unlock(&sess->mtx); + assert(res == thrd_success); + return 0; + } + if (sess->extra_line_cb) { + line[received] = 0; + res = sess->extra_line_cb(line, received, sess->extra_line_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + } + } + return DEUCE_SSH_ERROR_TERMINATED; +} + +static int +rx_thread(void *arg) +{ + deuce_ssh_session_t sess = arg; + int res; + + /* Handshake */ + res = version_ex(sess); + if (res < 0) + return res; + + while (!sess->terminate) { + // Read enough to get the length + // Allocate space for packet + // Read the rest of the packet + // Decrypt + // Decompress + // Handle + } + return 0; +} + +static int +tx_handshake(void *arg) +{ + deuce_ssh_session_t sess = arg; + int res; + + /* Handshake */ + res = sess->tx((uint8_t *)"SSH-2.0-", 8, &sess->terminate, sess->tx_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + size_t sz = strlen(sess->software_version); + res = sess->tx((uint8_t *)sess->software_version, sz, &sess->terminate, sess->tx_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + if (sess->version_comment != NULL) { + res = sess->tx((uint8_t *)" ", 1, &sess->terminate, sess->tx_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + sz = strlen(sess->version_comment); + res = sess->tx((uint8_t *)sess->software_version, sz, &sess->terminate, sess->tx_cbdata); + if (res < 0) { + sess->terminate = true; + return res; + } + } + return 0; +} + +void +deuce_ssh_transport_cleanup(deuce_ssh_session_t sess) +{ + free(sess->remote_software_version); + sess->remote_software_version = NULL; + free(sess->remote_version_comment); + sess->remote_version_comment = NULL; +} + +int +deuce_ssh_transport_init(deuce_ssh_session_t sess) +{ + if (sess->software_version == NULL) + sess->software_version = sw_ver; + + thrd_t thrd; + if (thrd_create(&thrd, rx_thread, sess) != thrd_success) + return DEUCE_SSH_ERROR_INIT; + int res = tx_handshake(sess); + if (res < 0) { + sess->terminate = true; + int tres; + thrd_join(thrd, &tres); + return res; + } + + sess->transport_thread = thrd; + return 0; +} diff --git a/src/ssh/ssh-trans.h b/src/ssh/ssh-trans.h index 6aa26fe8b7..11e68f2337 100644 --- a/src/ssh/ssh-trans.h +++ b/src/ssh/ssh-trans.h @@ -1,5 +1,7 @@ // RFC-4253 +#include "ssh-arch.h" + #ifndef DEUCE_SSH_TRANS_H #define DEUCE_SSH_TRANS_H @@ -14,4 +16,38 @@ #define SSH_MSG_KEXINIT 20 #define SSH_MSG_NEWKEYS 21 +/* SSH_MSG_DISCONNECT reason codes */ +#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH_DISCONNECT_RESERVED 4 +#define SSH_DISCONNECT_MAC_ERROR 5 +#define SSH_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH_DISCONNECT_CONNECTION_LOST 10 +#define SSH_DISCONNECT_BY_APPLICATION 11 +#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 + +struct deuce_ssh_transport_packet { + deuce_ssh_string_t payload; + deuce_ssh_string_t random_padding; + deuce_ssh_string_t mac; + deuce_ssh_uint32_t packet_length; + deuce_ssh_byte_t padding; +}; + +struct deuce_ssh_transport_state { + uint32_t tx_seq; + uint32_t rx_seq; + bool client; +}; + +int deuce_ssh_transport_init(deuce_ssh_session_t sess); +void deuce_ssh_transport_cleanup(deuce_ssh_session_t sess); + #endif diff --git a/src/ssh/ssh.c b/src/ssh/ssh.c new file mode 100644 index 0000000000..4b645ee0de --- /dev/null +++ b/src/ssh/ssh.c @@ -0,0 +1,40 @@ +#include "ssh.h" +#include "ssh-trans.h" + +int +deuce_ssh_session_init(deuce_ssh_session_t sess) +{ + int res = mtx_init(&sess->mtx, mtx_plain); + if (res != thrd_success) + return DEUCE_SSH_ERROR_INIT; + + res = deuce_ssh_transport_init(sess); + if (res < 0) { + return res; + } + + sess->initialized = true; + return 0; +} + +bool +deuce_ssh_session_terminate(deuce_ssh_session_t sess) +{ + bool t = true; + if (atomic_compare_exchange_strong(&sess->initialized, &t, false)) { + sess->terminate = true; + int tres; + thrd_join(sess->transport_thread, &tres); + sess->terminate = false; + return true; + } + return false; +} + +void +deuce_ssh_session_cleanup(deuce_ssh_session_t sess) +{ + deuce_ssh_transport_cleanup(sess); + deuce_ssh_session_terminate(sess); + mtx_destroy(&sess->mtx); +} diff --git a/src/ssh/ssh.h b/src/ssh/ssh.h index 4fa83df222..7036cbad02 100644 --- a/src/ssh/ssh.h +++ b/src/ssh/ssh.h @@ -1,11 +1,52 @@ #ifndef DEUCE_SSH_H #define DEUCE_SSH_H +#ifdef __STDC_NO_ATOMICS__ +_Static_assert(0, "stdatomic.h support required"); +#endif + +#ifdef __STDC_NO_THREADS__ +_Static_assert(0, "threads.h support required"); +#endif + #include <inttypes.h> +#include <stdatomic.h> +#include <stdbool.h> +#include <threads.h> + +#define DEUCE_SSH_ERROR_NONE 0 +#define DEUCE_SSH_ERROR_PARSE -1 +#define DEUCE_SSH_ERROR_INVALID -2 +#define DEUCE_SSH_ERROR_ALLOC -3 +#define DEUCE_SSH_ERROR_INIT -4 +#define DEUCE_SSH_ERROR_TERMINATED -5 + +typedef struct deuce_ssh_session { + /* Global */ + mtx_t mtx; + atomic_bool initialized; + atomic_bool terminate; + + /* Transport options */ + const char *software_version; + const char *version_comment; + void *tx_cbdata; + int (*tx)(uint8_t *buf, size_t bufsz, atomic_bool *terminate, void *cbdata); + void *rx_cbdata; + int (*rx)(uint8_t *buf, size_t bufsz, atomic_bool *terminate, void *cbdata); + void *rx_line_cbdata; + int (*rx_line)(uint8_t *buf, size_t bufsz, size_t *bytes_received, atomic_bool *terminate, void *cbdata); + void *extra_line_cbdata; + int (*extra_line_cb)(uint8_t *buf, size_t bufsz, void *cbdata); + thrd_t transport_thread; + + /* Transport Remote information */ + char *remote_software_version; + char *remote_version_comment; +} *deuce_ssh_session_t; -#define DEUCE_SSH_ERROR_NONE 0 -#define DEUCE_SSH_ERROR_PARSE -1 -#define DEUCE_SSH_ERROR_INVALID -2 -#define DEUCE_SSH_ERROR_ALLOC -3 +int deuce_ssh_session_init(deuce_ssh_session_t sess); +bool deuce_ssh_session_terminate(deuce_ssh_session_t sess); +void deuce_ssh_session_cleanup(deuce_ssh_session_t sess); #endif -- GitLab