aboutsummaryrefslogtreecommitdiff
path: root/ssh/server.c
diff options
context:
space:
mode:
authorTom Smeding <tom.smeding@gmail.com>2020-07-12 12:12:54 +0200
committerTom Smeding <tom.smeding@gmail.com>2020-07-12 12:12:54 +0200
commit7cfede3e206592ad0e4cb7a3a63a1e18ee7a5bad (patch)
treeaa177597fd77b1388e26df4579d796042309ac11 /ssh/server.c
parenta0672edaeaf9f4da0e9f6bf6dbe787e1386fa713 (diff)
ssh: Some executable renamings
Diffstat (limited to 'ssh/server.c')
-rw-r--r--ssh/server.c594
1 files changed, 0 insertions, 594 deletions
diff --git a/ssh/server.c b/ssh/server.c
deleted file mode 100644
index 84f0587..0000000
--- a/ssh/server.c
+++ /dev/null
@@ -1,594 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdatomic.h>
-#include <string.h>
-#include <ctype.h>
-#include <errno.h>
-#include <assert.h>
-#include <pthread.h>
-#include <netdb.h>
-#include <poll.h>
-#include <signal.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <arpa/inet.h>
-#include <libssh/server.h>
-#include <libssh/callbacks.h>
-#include "util.h"
-
-
-#define RESOURCE_ERROR_SLEEP_MS 10000
-
-static const bool debug_enabled = false;
-
-
-__attribute__((format (printf, 1, 2)))
-static void debug(const char *restrict format, ...) {
- if (debug_enabled) {
- va_list ap;
- va_start(ap, format);
- vfprintf(stderr, format, ap);
- va_end(ap);
- }
-}
-
-static void xxd(FILE *stream, const void *buf_, size_t length) {
- unsigned char *buf = (unsigned char*)buf_;
-
- for (size_t cursor = 0; cursor < length;) {
- fprintf(stream, "%08zx:", cursor);
-
- for (int i = 0; i < 16; i++) {
- if (i % 2 == 0) fprintf(stream, " ");
- if (i % 8 == 0) fprintf(stream, " ");
- if (cursor + i < length) fprintf(stream, "%02x", (unsigned)buf[cursor + i]);
- else fprintf(stream, " ");
- }
-
- fprintf(stream, " |");
-
- for (int i = 0; i < 16 && cursor + i < length; i++) {
- if (isprint(buf[cursor + i])) fprintf(stream, "%c", buf[cursor + i]);
- else fprintf(stream, ".");
- }
-
- fprintf(stream, "|\n");
-
- cursor += 16;
- }
-}
-
-static atomic_int g_thread_count;
-
-struct thread_data {
- struct addrinfo backend_addr;
-
- int backend_fd;
-
- int thread_id;
- ssh_session session;
- ssh_channel channel; // NULL before channel has been opened
- bool should_close;
-
- struct ssh_server_callbacks_struct server_cb;
- struct ssh_channel_callbacks_struct chan_cb;
-};
-
-///////// CHANNEL CALLBACKS //////////
-
-static int channel_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_;
- if (strcmp(subsystem, "tomsg") == 0) {
- debug("[%d] subsystem request: <%s>, allowing\n", tdata->thread_id, subsystem);
- return 0;
- } else {
- debug("[%d] subsystem request: <%s>, denying!\n", tdata->thread_id, subsystem);
- return 1;
- }
-}
-
-static void channel_close_cb(ssh_session session, ssh_channel channel, void *tdata_) {
- (void)session; (void)channel;
- struct thread_data *tdata = (struct thread_data*)tdata_;
- debug("[%d] channel close!\n", tdata->thread_id);
-}
-
-static void channel_eof_cb(ssh_session session, ssh_channel channel, void *tdata_) {
- (void)session; (void)channel;
- struct thread_data *tdata = (struct thread_data*)tdata_;
- debug("[%d] eof on channel, setting close flag\n", tdata->thread_id);
- tdata->should_close = true;
-}
-
-static int channel_data_cb(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *tdata_) {
- (void)is_stderr; (void)data; (void)channel; (void)session;
- struct thread_data *tdata = (struct thread_data*)tdata_;
-
- debug("[%d] data on channel (length %u):\n", tdata->thread_id, len);
- if (debug_enabled) xxd(stdout, data, len);
-
- // debug("[%d] echoing back!\n", tdata->thread_id);
- // if (ssh_channel_write(channel, data, len) == SSH_ERROR) {
- // debug("[%d] write to channel failed! Setting close flag\n", tdata->thread_id);
- // tdata->should_close = true;
- // }
-
- const char *start = (const char*)data;
- const char *cursor = start;
- const char *end = start + len;
-
- while (cursor < end) {
- ssize_t nw = write(tdata->backend_fd, cursor, end - cursor);
- if (nw < 0) {
- if (errno == EINTR) continue;
- debug("[%d] error writing to backend socket: %s\n", tdata->thread_id, strerror(errno));
- tdata->should_close = true;
- return cursor - start;
- }
- if (nw == 0) { // should not happen?
- debug("[%d] write(2) returned 0?\n", tdata->thread_id);
- tdata->should_close = true;
- return cursor - start;
- }
- cursor += nw;
- }
-
- return len;
-}
-
-////////// SERVER CALLBACKS //////////
-
-static int auth_none_cb(ssh_session session, const char *user, void *tdata_) {
- (void)session;
- struct thread_data *tdata = (struct thread_data*)tdata_;
- debug("[%d] auth none (user <%s>), accepting\n", tdata->thread_id, user);
- return SSH_AUTH_SUCCESS;
-}
-
-static int service_request_cb(ssh_session session, const char *service, void *tdata_) {
- (void)session;
- struct thread_data *tdata = (struct thread_data*)tdata_;
- if (strcmp(service, "ssh-userauth") == 0) {
- debug("[%d] ssh-userauth service request, allowing through\n", tdata->thread_id);
- return 0;
- } else {
- debug("[%d] service request <%s>, not allowing\n", tdata->thread_id, service);
- return -1;
- }
-}
-
-static 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) == SSH_OK) {
- debug("[%d] channel open request, allowing\n", tdata->thread_id);
- tdata->channel = chan;
- return chan;
- }
- ssh_channel_close(chan);
- ssh_channel_free(chan);
- }
- }
- debug("[%d] channel open request, denying!\n", tdata->thread_id);
- return NULL;
-}
-
-static int backend_data_cb(int fd, int revents, void *tdata_) {
- struct thread_data *tdata = (struct thread_data*)tdata_;
-
- if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
- // Print this always, because backend issues are interesting
- char descr[64] = "";
- if (revents & POLLERR) strcat(descr, "|POLLERR");
- if (revents & POLLHUP) strcat(descr, "|POLLHUP");
- if (revents & POLLNVAL) strcat(descr, "|POLLNVAL");
- printf("[%d] %s on backend\n", tdata->thread_id, descr + 1);
- close(fd);
- tdata->should_close = true;
- }
-
- if (revents & POLLIN) {
- char buffer[1024];
- ssize_t nr = read(fd, buffer, sizeof buffer);
- if (nr < 0) {
- if (errno == EINTR) return 0;
- // Backend issues are interesting, not noise
- printf("[%d] Error reading from backend socket: %s\n", tdata->thread_id, strerror(errno));
- tdata->should_close = true;
- return 0;
- }
-
- if (nr == 0) { // eof
- tdata->should_close = true;
- return 0;
- }
-
- int cursor = 0;
- while (cursor < nr) {
- int nw = ssh_channel_write(tdata->channel, buffer + cursor, nr - cursor);
- if (nw == SSH_ERROR) {
- debug("[%d] Error writing to ssh channel: %s\n", tdata->thread_id, ssh_get_error(tdata->channel));
- tdata->should_close = true;
- return 0;
- }
- cursor += nw;
- }
- }
-
- return 0;
-}
-
-static void print_addrinfo(FILE *stream, const struct addrinfo *info) {
- if (info->ai_family == AF_INET) fprintf(stream, "inet ");
- else if (info->ai_family == AF_INET6) fprintf(stream, "inet6 ");
- else fprintf(stream, "(family=%d) ", info->ai_family);
-
- if (info->ai_socktype == SOCK_STREAM) fprintf(stream, "stream ");
- else if (info->ai_socktype == SOCK_DGRAM) fprintf(stream, "datagram ");
- else fprintf(stream, "(socktype=%d) ", info->ai_socktype);
-
- if (info->ai_protocol == IPPROTO_TCP) fprintf(stream, "TCP ");
- else if (info->ai_protocol == IPPROTO_UDP) fprintf(stream, "UDP ");
- else fprintf(stream, "(protocol=%d) ", info->ai_protocol);
-
- bool success = false;
- if (info->ai_family == AF_INET) {
- char addrbuf[INET_ADDRSTRLEN];
- struct sockaddr_in *sin = (struct sockaddr_in*)info->ai_addr;
- if (inet_ntop(AF_INET, &sin->sin_addr, addrbuf, INET_ADDRSTRLEN)) {
- fprintf(stream, "%s\n", addrbuf);
- success = true;
- }
- } else if (info->ai_family == AF_INET6) {
- char addrbuf[INET6_ADDRSTRLEN];
- struct sockaddr_in6 *sin = (struct sockaddr_in6*)info->ai_addr;
- if (inet_ntop(AF_INET6, &sin->sin6_addr, addrbuf, INET6_ADDRSTRLEN)) {
- fprintf(stream, "%s\n", addrbuf);
- success = true;
- }
- }
- if (!success) {
- fprintf(stream, "(unknown address format: %s)\n", strerror(errno));
- }
-}
-
-// Returns whether successful.
-static bool lookup_backend(const char *host, int port, struct addrinfo *dst) {
- char port_string[16];
- sprintf(port_string, "%d", port);
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof hints);
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_flags = AI_ADDRCONFIG;
-
- struct addrinfo *result;
- int ret = getaddrinfo(host, port_string, &hints, &result);
-
- if (ret < 0) {
- fprintf(stderr, "Could not resolve backend: %s\n", gai_strerror(ret));
- return false;
- }
-
- int last_failure = 0;
- bool success = false;
- for (struct addrinfo *item = result; item; ) {
- debug("lookup_backend: option ");
- if (debug_enabled) print_addrinfo(stdout, item);
-
- int sock = socket(item->ai_family, item->ai_socktype, item->ai_protocol);
- if (sock == -1) {
- last_failure = errno;
- debug(" socket() failure: %s\n", strerror(last_failure));
- continue;
- }
-
- int ret = connect(sock, item->ai_addr, item->ai_addrlen);
- last_failure = errno;
- close(sock);
-
- if (ret == 0) {
- debug(" success!\n");
- success = true;
- // Free the rest of the linked list, keeping this item intact.
- freeaddrinfo(item->ai_next);
- *dst = *item;
- dst->ai_next = NULL;
- break;
- } else {
- debug(" connect() failure: %s\n", strerror(last_failure));
- }
-
- debug(" next=%p\n", item->ai_next);
-
- // Free this element in the linked list, but preserve (and switch to) the tail.
- struct addrinfo *next = item->ai_next;
- item->ai_next = NULL;
- freeaddrinfo(item);
- item = next;
- }
-
- if (success) {
- return true;
- } else {
- fprintf(stderr, "Could not connect to backend: %s\n", strerror(last_failure));
- return false;
- }
-}
-
-static int connect_backend(const struct thread_data *tdata) {
- const struct addrinfo *item = &tdata->backend_addr;
- int sock = socket(item->ai_family, item->ai_socktype, item->ai_protocol);
- if (sock == -1) return -1;
-
- if (connect(sock, item->ai_addr, item->ai_addrlen) == 0) {
- debug("connect_backend: sock=%d\n", sock);
- return sock;
- }
-
- close(sock);
- return -1;
-}
-
-static 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;
-
- debug("[%d] Thread started\n", tid);
-
- 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;
- tdata->server_cb.service_request_function = service_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 = channel_subsystem_request_cb;
- tdata->chan_cb.channel_close_function = channel_close_cb;
- tdata->chan_cb.channel_eof_function = channel_eof_cb;
- tdata->chan_cb.channel_data_function = channel_data_cb;
-
- if (ssh_set_server_callbacks(session, &tdata->server_cb) != SSH_OK) {
- debug("[%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) {
- debug("[%d] Key exchange failed: %s\n", tid, ssh_get_error(session));
- goto cleanup;
- }
- debug("[%d] Handled key exchange\n", tid);
-
- tdata->backend_fd = connect_backend(tdata);
- if (tdata->backend_fd == -1) {
- debug("[%d] Failed to connect to backend: %s\n", tid, strerror(errno));
- goto cleanup;
- }
-
- debug("[%d] Connected to backend (fd=%d)\n", tid, tdata->backend_fd);
-
- event = ssh_event_new();
- if (!event
- || ssh_event_add_session(event, session) != SSH_OK
- || ssh_event_add_fd(event, tdata->backend_fd, POLLIN, backend_data_cb, tdata) != SSH_OK) {
- debug("[%d] Failed to create ssh event context\n", tid);
- goto cleanup;
- }
-
- while (!tdata->should_close) {
- // 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) {
- debug("[%d] read pending?\n", tid);
- }
- }
-
-cleanup:
- if (tdata->backend_fd != -1) close(tdata->backend_fd);
- 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);
- debug("[%d] Disconnected\n", tid);
- }
- free(tdata);
- int num_threads = atomic_fetch_sub(&g_thread_count, 1);
- debug("[%d] Exiting! (%d threads remaining)\n", tid, num_threads - 1);
- return NULL;
-}
-
-static void generate_key(const char *outfile) {
- ssh_key host_key;
- int ret = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &host_key);
- if (ret != SSH_OK) {
- fprintf(stderr, "Key generation failed (RSA4096)!\n");
- exit(1);
- }
-
- ret = ssh_pki_export_privkey_file(host_key, NULL, NULL, NULL, outfile);
- if (ret != SSH_OK) {
- fprintf(stderr, "Failed to export generated host key to file '%s'; is that location accessible?\n", outfile);
- exit(1);
- }
-
- if (chmod(outfile, S_IRUSR | S_IWUSR) != 0) {
- fprintf(stderr, "Failed to set mode 600 on generated host key file '%s'; this is insecure!\n", outfile);
- exit(1);
- }
-
- printf("RSA4096 host key generated and written to '%s'.\n", outfile);
-}
-
-static void usage(const char *argv0) {
- fprintf(stderr,
- "Usage: %s <ssh host key file> <ssh port> [backendhost:port]\n"
- " %s --generate <host key output file>\n"
- "SSH-TCP bridge for tomsg. Accepts SSH connections with a channel for subsystem\n"
- "'tomsg', and matches each SSH connection with a plain TCP connection to the\n"
- "backend server (which defaults to localhost:29536). All data is forwarded\n"
- "transparently.\n"
- "Use the '--generate' form to generate a host key for use in the main invocation\n"
- "form.\n",
- argv0, argv0);
-}
-
-int main(int argc, char **argv) {
- const char *host_key_fname;
- int ssh_port = 2222;
- const char *backend_host = "localhost";
- int backend_port = 29536;
-
- if (argc == 3 && strcmp(argv[1], "--generate") == 0) {
- generate_key(argv[2]);
- return 0;
- } else if (3 <= argc && argc <= 4) {
- host_key_fname = argv[1];
- char *endp;
- ssh_port = strtol(argv[2], &endp, 10);
- if (argv[2][0] == '\0' || *endp != '\0' || ssh_port < 0 || ssh_port > 65535) {
- fprintf(stderr, "Cannot parse port number from argument '%s'\n", argv[2]);
- return 1;
- }
- if (argc == 4) {
- if (!parse_host_port(argv[3], &backend_host, &backend_port)) {
- fprintf(stderr, "Cannot parse host:port from argument '%s'\n", argv[3]);
- return 1;
- }
- }
- } else {
- usage(argv[0]);
- return 1;
- }
-
- // We prefer to detect socket closure through return codes, not signals.
- signal(SIGPIPE, SIG_IGN);
-
- if (ssh_init() != SSH_OK) {
- fprintf(stderr, "Could not initialise libssh\n");
- return 1;
- }
-
- ssh_key host_key;
- if (ssh_pki_import_privkey_file(host_key_fname, NULL, NULL, NULL, &host_key) != SSH_OK) {
- fprintf(stderr, "Failed to read host private key file '%s'\n", host_key_fname);
- return 1;
- }
-
- size_t host_key_hash_length = 0;
- unsigned char *host_key_hash = NULL;
- if (ssh_get_publickey_hash(host_key, SSH_PUBLICKEY_HASH_SHA256, &host_key_hash, &host_key_hash_length) != SSH_OK) {
- fprintf(stderr, "Failed to hash host key!\n");
- return 1;
- }
-
- printf("Host key hash: ");
- fflush(stdout);
- ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, host_key_hash, host_key_hash_length);
-
- ssh_bind srvbind = ssh_bind_new();
- if (!srvbind) {
- fprintf(stderr, "Failed to create new bind socket\n");
- return 1;
- }
-
- bool procconfig = false;
- const char *ciphers_str = "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc";
- bool ok = true;
- ok &= ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &procconfig) == SSH_OK;
- ok &= ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_BINDPORT, &ssh_port) == SSH_OK;
- ok &= ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_IMPORT_KEY, host_key) == SSH_OK;
- ok &= ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_CIPHERS_C_S, ciphers_str) == SSH_OK;
- ok &= ssh_bind_options_set(srvbind, SSH_BIND_OPTIONS_CIPHERS_S_C, ciphers_str) == SSH_OK;
-
- if (!ok) {
- fprintf(stderr, "Could not set options on SSH bind socket: %s\n", ssh_get_error(srvbind));
- return 1;
- }
-
- if (ssh_bind_listen(srvbind) != SSH_OK) {
- fprintf(stderr, "Could not listen on SSH bind socket: %s\n", ssh_get_error(srvbind));
- return 1;
- }
-
- struct addrinfo backend_addr;
- if (!lookup_backend(backend_host, backend_port, &backend_addr)) {
- // Error already printed in lookup_backend
- return 1;
- }
-
- printf("Listening for SSH connections on port %d\n", ssh_port);
- printf("Forwarding to backend at %s:%d\n", backend_host, backend_port);
-
- 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) {
- ssh_session session = ssh_new();
- if (!session) {
- fprintf(stderr, "ERROR: Cannot create new SSH session object!\n");
- usleep(1000 * RESOURCE_ERROR_SLEEP_MS);
- continue;
- }
-
- if (ssh_bind_accept(srvbind, session) != SSH_OK) {
- fprintf(stderr, "ERROR: Cannot accept on bind socket: %s", ssh_get_error(srvbind));
- ssh_free(session);
- usleep(1000 * RESOURCE_ERROR_SLEEP_MS);
- continue;
- }
-
- int num_existing_threads = atomic_fetch_add(&g_thread_count, 1);
- debug("Accepted connection, spinning up thread (currently %d threads)\n",
- num_existing_threads + 1);
-
- struct thread_data *tdata = calloc(1, sizeof(struct thread_data));
- if (!tdata) {
- fprintf(stderr, "ERROR: Out of memory, cannot allocate thread_data!\n");
- ssh_disconnect(session);
- ssh_free(session);
- usleep(1000 * RESOURCE_ERROR_SLEEP_MS);
- continue;
- }
-
- tdata->backend_addr = backend_addr;
- tdata->backend_fd = -1;
- tdata->thread_id = next_thread_id++;
- tdata->session = session;
- tdata->channel = NULL;
- tdata->should_close = false;
-
- pthread_t thread;
- if (pthread_create(&thread, &thread_attrs, thread_entry, tdata) != 0) {
- fprintf(stderr, "ERROR: Could not spawn thread: %s!\n", strerror(errno));
- free(tdata);
- ssh_disconnect(session);
- ssh_free(session);
- usleep(1000 * RESOURCE_ERROR_SLEEP_MS);
- continue;
- }
- }
-}