aboutsummaryrefslogtreecommitdiff
path: root/ssh
diff options
context:
space:
mode:
authorTom Smeding <tom.smeding@gmail.com>2020-06-22 23:24:57 +0200
committerTom Smeding <tom.smeding@gmail.com>2020-06-25 17:21:29 +0200
commit95dafc361c5715d09f55360d488fd3ede97d1438 (patch)
tree6d5b48f71f7a53736030bd2860e6b2d17d1eac96 /ssh
parent46feba9a7b1e4ed023dcdc3c505b67f3eb8348f0 (diff)
WIP server using libssh, TODO is custom client
I think writing a custom client may work better with the callback structure, though I'm not sure
Diffstat (limited to 'ssh')
-rw-r--r--ssh/.gitignore5
-rw-r--r--ssh/Makefile19
-rw-r--r--ssh/server.c302
3 files changed, 326 insertions, 0 deletions
diff --git a/ssh/.gitignore b/ssh/.gitignore
new file mode 100644
index 0000000..2cb4873
--- /dev/null
+++ b/ssh/.gitignore
@@ -0,0 +1,5 @@
+host_key
+host_key.pub
+server
+*.o
+compile_commands.json
diff --git a/ssh/Makefile b/ssh/Makefile
new file mode 100644
index 0000000..63cb462
--- /dev/null
+++ b/ssh/Makefile
@@ -0,0 +1,19 @@
+CC = gcc
+CFLAGS = -Wall -Wextra -std=c11 -g -O2 -fwrapv -pthread -D_DEFAULT_SOURCE
+LDFLAGS = -pthread
+CFLAGS += $(shell pkg-config --cflags libssh)
+LDFLAGS += $(shell pkg-config --libs libssh)
+
+.PHONY: all clean
+
+all: server
+
+clean:
+ rm -f server *.o
+
+
+server: server.o ../global.o ../memory.o
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+%.o: %.c $(wildcard *.h)
+ $(CC) $(CFLAGS) -c -o $@ $<
diff --git a/ssh/server.c b/ssh/server.c
new file mode 100644
index 0000000..55a53c2
--- /dev/null
+++ b/ssh/server.c
@@ -0,0 +1,302 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <pthread.h>
+#include <sys/select.h>
+#include <libssh/server.h>
+#include <libssh/callbacks.h>
+#include "../global.h"
+
+
+#define CHECK(obj_, expr_) do { \
+ if (!(expr_)) { \
+ fprintf(stderr, "libssh error! expression: " #expr_ "\nError description: %s\n", \
+ ssh_get_error((obj_))); \
+ exit(1); \
+ } \
+ } while (0)
+
+// struct sessions {
+// // Always NULL-terminated
+// ssh_session *list;
+// ssh_session *outlist; // same length as 'list', contents not managed
+// size_t cap, len;
+// };
+
+// static struct sessions sessions_make() {
+// size_t cap = 2;
+// return (struct sessions) {
+// .list = malloc(cap, ssh_session),
+// .outlist = malloc(cap, ssh_session),
+// .cap = cap,
+// .len = 0,
+// };
+// }
+
+// static void sessions_add(struct sessions *ss, ssh_session ses) {
+// if (ss->len + 1 >= ss->cap) {
+// ss->cap *= 2;
+// ss->list = realloc(ss->list, ss->cap, ssh_session);
+// ss->outlist = realloc(ss->outlist, ss->cap, ssh_session);
+// }
+// ss->list[ss->len++] = ses;
+// ss->list[ss->len] = NULL;
+// }
+
+// static void sessions_remove(struct sessions *ss, size_t index) {
+// assert(0 <= index && index < ss->len);
+// if (index < ss->len - 1) ss->list[index] = ss->list[ss->len - 1];
+// ss->len--;
+// ss->list[ss->len] = NULL;
+// }
+
+static atomic_int g_thread_count;
+
+struct thread_data {
+ int thread_id;
+ ssh_session session;
+ ssh_channel channel; // NULL before channel has been opened
+
+ struct ssh_server_callbacks_struct server_cb;
+ struct ssh_channel_callbacks_struct chan_cb;
+};
+
+int subsystem_request_cb(ssh_session session, ssh_channel channel, const char *subsystem, void *tdata_) {
+ (void)session;
+ (void)channel;
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ printf("[%d] subsystem request: <%s>\n", tdata->thread_id, subsystem);
+ return 1;
+}
+
+void channel_close_cb(ssh_session session, ssh_channel channel, void *tdata_) {
+ (void)session; (void)channel;
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ printf("[%d] channel close!\n", tdata->thread_id);
+}
+
+int channel_shell_request_cb(ssh_session session, ssh_channel channel, void *tdata_) {
+ (void)session; (void)channel;
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ printf("[%d] shell request, denying\n", tdata->thread_id);
+ return 1;
+}
+
+void channel_eof_cb(ssh_session session, ssh_channel channel, void *tdata_) {
+ (void)session; (void)channel;
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ printf("[%d] eof on channel\n", tdata->thread_id);
+}
+
+int auth_none_cb(ssh_session session, const char *user, void *tdata_) {
+ (void)session;
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ printf("[%d] auth none (user <%s>), accepting\n", tdata->thread_id, user);
+ return SSH_AUTH_SUCCESS;
+}
+
+ssh_channel chan_open_request_cb(ssh_session session, void *tdata_) {
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ if (tdata->channel == NULL) {
+ ssh_channel chan = ssh_channel_new(session);
+ if (chan != NULL) {
+ if (ssh_set_channel_callbacks(chan, &tdata->chan_cb)) {
+ tdata->channel = chan;
+ return chan;
+ }
+ ssh_channel_close(chan);
+ ssh_channel_free(chan);
+ }
+ }
+ return NULL;
+}
+
+// int msg_callback(ssh_session session, ssh_message msg, void *tdata_) {
+// (void)session;
+// struct thread_data *tdata = (struct thread_data*)tdata_;
+// const int tid = tdata->thread_id;
+
+// const int subtype = ssh_message_subtype(msg);
+
+// switch (ssh_message_type(msg)) {
+// case SSH_REQUEST_AUTH:
+// printf("[%d] message callback: type auth (subtype %d)\n", tid, subtype);
+// if (subtype == SSH_AUTH_METHOD_NONE) {
+// if (ssh_message_auth_reply_success(msg, false) == SSH_OK) {
+// return 0; // handled
+// } else {
+// printf("[%d] failed to reply success for auth method none\n", tid);
+// }
+// }
+// break;
+
+// case SSH_REQUEST_CHANNEL_OPEN:
+// printf("[%d] message callback: type channel open (subtype %d)\n", tid, subtype);
+// if (tdata->channel == NULL && subtype == SSH_CHANNEL_SESSION) {
+// ssh_channel chan = ssh_message_channel_request_open_reply_accept(msg);
+// chan_cb.userdata = tdata;
+
+// if (chan && ssh_set_channel_callbacks(chan, &chan_cb) == SSH_OK) {
+// tdata->channel = chan;
+// return 0; // handled
+// } else {
+// if (chan) {
+// ssh_channel_close(chan);
+// ssh_channel_free(chan);
+// }
+// printf("[%d] failed to accept channel open request for session\n", tid);
+// }
+// }
+// break;
+
+// case SSH_REQUEST_CHANNEL:
+// printf("[%d] message callback: type channel (subtype %d)\n", tid, subtype);
+// break;
+
+// case SSH_REQUEST_SERVICE:
+// printf("[%d] message callback: type service\n", tid);
+// break;
+
+// case SSH_REQUEST_GLOBAL:
+// printf("[%d] message callback: type global (subtype %d)\n", tid, subtype);
+// break;
+// }
+
+// return 1; // not handled
+// }
+
+void* thread_entry(void *tdata_) {
+ struct thread_data *tdata = (struct thread_data*)tdata_;
+ const int tid = tdata->thread_id;
+ const ssh_session session = tdata->session;
+ ssh_event event = NULL;
+
+ printf("[%d] Thread started\n", tid);
+
+ // ssh_set_message_callback(session, msg_callback, tdata);
+
+ memset(&tdata->server_cb, 0, sizeof tdata->server_cb);
+ ssh_callbacks_init(&tdata->server_cb);
+ tdata->server_cb.userdata = tdata;
+ tdata->server_cb.auth_none_function = auth_none_cb;
+ tdata->server_cb.channel_open_request_session_function = chan_open_request_cb;
+
+ memset(&tdata->chan_cb, 0, sizeof tdata->chan_cb);
+ ssh_callbacks_init(&tdata->chan_cb);
+ tdata->chan_cb.userdata = tdata;
+ tdata->chan_cb.channel_subsystem_request_function = subsystem_request_cb;
+ tdata->chan_cb.channel_close_function = channel_close_cb;
+ tdata->chan_cb.channel_shell_request_function = channel_shell_request_cb;
+ tdata->chan_cb.channel_eof_function = channel_eof_cb;;
+
+ if (ssh_set_server_callbacks(session, &tdata->server_cb) != SSH_OK) {
+ printf("[%d] Failed setting server callbacks: %s\n", tid, ssh_get_error(session));
+ goto cleanup;
+ }
+
+ ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE);
+
+ if (ssh_handle_key_exchange(session) != SSH_OK) {
+ printf("[%d] Key exchange failed: %s\n", tid, ssh_get_error(session));
+ goto cleanup;
+ }
+ printf("[%d] Handled key exchange\n", tid);
+
+ event = ssh_event_new();
+ if (!event || ssh_event_add_session(event, session) != SSH_OK) {
+ printf("[%d] Failed to create ssh event context\n", tid);
+ goto cleanup;
+ }
+
+ while (true) {
+ // printf("[%d] poll loop...\n", tid);
+ ssh_event_dopoll(event, -1);
+ int status = ssh_get_status(session);
+ if (status & (SSH_CLOSED | SSH_CLOSED_ERROR)) goto cleanup;
+ if (status & SSH_READ_PENDING) {
+ printf("[%d] read pending?\n", tid);
+ }
+ }
+
+cleanup:
+ if (event) ssh_event_free(event);
+ if (tdata->channel) {
+ ssh_channel_close(tdata->channel);
+ ssh_channel_free(tdata->channel);
+ }
+ if (session) {
+ ssh_disconnect(session);
+ ssh_free(session);
+ printf("[%d] Disconnected\n", tid);
+ }
+ free(tdata);
+ int num_threads = atomic_fetch_sub(&g_thread_count, 1);
+ printf("[%d] Exiting! (%d threads remaining)\n", tid, num_threads - 1);
+ pthread_exit(NULL);
+}
+
+int main(void) {
+ if (ssh_init() != SSH_OK) {
+ fprintf(stderr, "Could not initialise libssh\n");
+ return 1;
+ }
+
+ ssh_bind srvbind = ssh_bind_new();
+ CHECK(srvbind, srvbind);
+
+ bool procconfig = false;
+ CHECK(srvbind, ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &procconfig) == SSH_OK);
+ int port = 2222;
+ CHECK(srvbind, ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_BINDPORT, &port) == SSH_OK);
+ CHECK(srvbind, ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_HOSTKEY, "host_key") == SSH_OK);
+ const char *ciphers_str = "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc";
+ CHECK(srvbind, ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_CIPHERS_C_S, ciphers_str) == SSH_OK);
+ CHECK(srvbind, ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_CIPHERS_S_C, ciphers_str) == SSH_OK);
+
+ CHECK(srvbind, ssh_bind_listen(srvbind) == SSH_OK);
+ printf("Listening for SSH connections on port %d\n", port);
+
+ // int srvbind_fd = ssh_bind_get_fd(srvbind);
+
+ // struct sessions sessions = sessions_make();
+
+ pthread_attr_t thread_attrs;
+ assert(pthread_attr_init(&thread_attrs) == 0);
+ assert(pthread_attr_setdetachstate(&thread_attrs, PTHREAD_CREATE_DETACHED) == 0);
+
+ int next_thread_id = 0;
+ atomic_store(&g_thread_count, 0);
+
+ while (true) {
+ // fd_set inset;
+ // FD_ZERO(&inset);
+ // FD_SET(srvbind_fd, &inset);
+
+ // int ret = ssh_select(sessions.list, sessions.outlist, srvbind_fd + 1, &inset, NULL);
+ // if (ret == SSH_EINTR) continue;
+ // if (ret == SSH_ERROR) {
+ // fprintf(stderr, "ssh_select reported error!\n");
+ // return 1;
+ // }
+
+ ssh_session session = ssh_new();
+ CHECK(session, session);
+
+ CHECK(srvbind, ssh_bind_accept(srvbind, session) == SSH_OK);
+ int num_existing_threads = atomic_fetch_add(&g_thread_count, 1);
+ printf("Accepted connection, spinning up thread (currently %d threads)\n",
+ num_existing_threads + 1);
+
+ struct thread_data *tdata = calloc(1, struct thread_data);
+ tdata->thread_id = next_thread_id++;
+ tdata->session = session;
+ tdata->channel = NULL;
+
+ pthread_t thread;
+ assert(pthread_create(&thread, &thread_attrs, thread_entry, tdata) == 0);
+ }
+}