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