From 7cfede3e206592ad0e4cb7a3a63a1e18ee7a5bad Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Sun, 12 Jul 2020 12:12:54 +0200 Subject: ssh: Some executable renamings --- ssh/.gitignore | 4 +- ssh/Makefile | 12 +- ssh/client.c | 153 -------------- ssh/server.c | 594 ----------------------------------------------------- ssh/server_proxy.c | 594 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ssh/ssh_client.c | 153 ++++++++++++++ 6 files changed, 756 insertions(+), 754 deletions(-) delete mode 100644 ssh/client.c delete mode 100644 ssh/server.c create mode 100644 ssh/server_proxy.c create mode 100644 ssh/ssh_client.c diff --git a/ssh/.gitignore b/ssh/.gitignore index 0abd297..fa45e11 100644 --- a/ssh/.gitignore +++ b/ssh/.gitignore @@ -1,6 +1,6 @@ ssh_host_key -server -client +server_proxy client_proxy +ssh_client *.o compile_commands.json diff --git a/ssh/Makefile b/ssh/Makefile index 76d23c9..487bb46 100644 --- a/ssh/Makefile +++ b/ssh/Makefile @@ -4,22 +4,24 @@ LDFLAGS = -pthread CFLAGS += $(shell pkg-config --cflags libssh) LDFLAGS += $(shell pkg-config --libs libssh) +TARGETS = server_proxy client_proxy ssh_client + .PHONY: all clean -all: server client client_proxy +all: $(TARGETS) clean: - rm -f server client *.o + rm -f $(TARGETS) *.o -server: server.o util.o +server_proxy: server_proxy.o util.o $(CC) -o $@ $^ $(LDFLAGS) -client: client.o sshnc.o util.o +client_proxy: client_proxy.o sshnc.o util.o $(CC) -o $@ $^ $(LDFLAGS) -client_proxy: client_proxy.o sshnc.o util.o +ssh_client: ssh_client.o sshnc.o util.o $(CC) -o $@ $^ $(LDFLAGS) %.o: %.c $(wildcard *.h) diff --git a/ssh/client.c b/ssh/client.c deleted file mode 100644 index 5c7f084..0000000 --- a/ssh/client.c +++ /dev/null @@ -1,153 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "util.h" -#include "sshnc.h" - - -static bool prompt_yn(const char *text) { - printf("%s", text); - fflush(stdout); - - bool response; - - char *line = NULL; - size_t linelen = 0; - while (true) { - ssize_t nr = getline(&line, &linelen, stdin); - if (nr == -1) { - perror("getline"); - exit(1); - } - - while (nr > 0 && isspace(line[nr - 1])) nr--; - - for (ssize_t i = 0; i < nr; i++) line[i] = tolower(line[i]); - - if ((nr == 1 && line[0] == 'y') || (nr == 3 && memcmp(line, "yes", 3) == 0)) { - response = true; - break; - } - if ((nr == 1 && line[0] == 'n') || (nr == 2 && memcmp(line, "no", 2) == 0)) { - response = false; - break; - } - - printf("Please answer with 'y', 'n', 'yes' or 'no'. [y/n]"); - fflush(stdout); - } - - free(line); - return response; -} - -static bool hostkey_checker(const unsigned char *hash, size_t length, void *userdata) { - (void)userdata; - printf("Server host key hash: %s\n", sshnc_print_hash(hash, length)); - - bool response = prompt_yn( - "Does this hash match the one given to you by the server administrator, or by a\n" - "member that you trust and is already connected to the server? [y/n] "); - if (!response) { - printf("Disconnecting.\n"); - } - - return response; -} - -int main(int argc, char **argv) { - const char *server_host = NULL; - int port = 2222; - - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - fprintf(stderr, "If port is not specified, %d is assumed.\n", port); - return 1; - } - - if (!parse_host_port(argv[1], &server_host, &port)) { - fprintf(stderr, "Cannot parse host:port from argument '%s'\n", argv[1]); - return 1; - } - - struct sshnc_client *client; - enum sshnc_retval ret = sshnc_connect( - server_host, port, "tomsg", "tomsg", hostkey_checker, NULL, &client); - - if (ret != SSHNC_OK) { - fprintf(stderr, "Could not connect: %s\n", sshnc_strerror(ret)); - return 1; - } - - struct pollfd polls[2]; - polls[0] = (struct pollfd){ - .fd = sshnc_poll_fd(client), - .events = POLLIN, - }; - polls[1] = (struct pollfd){ - .fd = STDIN_FILENO, - .events = POLLIN, - }; - - while (true) { - int pollret = poll(polls, sizeof polls / sizeof polls[0], -1); - if (pollret < 0) { - perror("poll"); - goto cleanup; - } - - if (polls[0].revents & (POLLERR | POLLNVAL)) { - fprintf(stderr, "Error reading from SSH socket\n"); - goto cleanup; - } - if (polls[1].revents & (POLLERR | POLLNVAL)) { - fprintf(stderr, "Error reading from stdin\n"); - goto cleanup; - } - - if (polls[0].revents & (POLLIN | POLLHUP)) { - char buffer[4096]; - size_t length = 0; - ret = sshnc_maybe_recv(client, sizeof buffer, buffer, &length); - if (ret == SSHNC_OK) { - fwrite(buffer, 1, length, stdout); - } else if (ret == SSHNC_EOF) { - break; - } else if (ret != SSHNC_AGAIN) { - fprintf(stderr, "Error on SSH recv: %s\n", sshnc_strerror(ret)); - goto cleanup; - } - } - - if (polls[1].revents & (POLLIN | POLLHUP)) { - char buffer[4096]; - ssize_t nr = read(STDIN_FILENO, buffer, sizeof buffer); - if (nr < 0) { - perror("Error reading from stdin"); - goto cleanup; - } - if (nr == 0) { - break; - } - - ret = sshnc_send(client, buffer, nr); - if (ret == SSHNC_EOF) { - break; - } else if (ret != SSHNC_OK) { - fprintf(stderr, "Error on SSH send: %s\n", sshnc_strerror(ret)); - goto cleanup; - } - } - } - - sshnc_close(client); - return 0; - -cleanup: - sshnc_close(client); - return 1; -} 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 [backendhost:port]\n" - " %s --generate \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; - } - } -} diff --git a/ssh/server_proxy.c b/ssh/server_proxy.c new file mode 100644 index 0000000..84f0587 --- /dev/null +++ b/ssh/server_proxy.c @@ -0,0 +1,594 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 [backendhost:port]\n" + " %s --generate \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; + } + } +} diff --git a/ssh/ssh_client.c b/ssh/ssh_client.c new file mode 100644 index 0000000..5c7f084 --- /dev/null +++ b/ssh/ssh_client.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "sshnc.h" + + +static bool prompt_yn(const char *text) { + printf("%s", text); + fflush(stdout); + + bool response; + + char *line = NULL; + size_t linelen = 0; + while (true) { + ssize_t nr = getline(&line, &linelen, stdin); + if (nr == -1) { + perror("getline"); + exit(1); + } + + while (nr > 0 && isspace(line[nr - 1])) nr--; + + for (ssize_t i = 0; i < nr; i++) line[i] = tolower(line[i]); + + if ((nr == 1 && line[0] == 'y') || (nr == 3 && memcmp(line, "yes", 3) == 0)) { + response = true; + break; + } + if ((nr == 1 && line[0] == 'n') || (nr == 2 && memcmp(line, "no", 2) == 0)) { + response = false; + break; + } + + printf("Please answer with 'y', 'n', 'yes' or 'no'. [y/n]"); + fflush(stdout); + } + + free(line); + return response; +} + +static bool hostkey_checker(const unsigned char *hash, size_t length, void *userdata) { + (void)userdata; + printf("Server host key hash: %s\n", sshnc_print_hash(hash, length)); + + bool response = prompt_yn( + "Does this hash match the one given to you by the server administrator, or by a\n" + "member that you trust and is already connected to the server? [y/n] "); + if (!response) { + printf("Disconnecting.\n"); + } + + return response; +} + +int main(int argc, char **argv) { + const char *server_host = NULL; + int port = 2222; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "If port is not specified, %d is assumed.\n", port); + return 1; + } + + if (!parse_host_port(argv[1], &server_host, &port)) { + fprintf(stderr, "Cannot parse host:port from argument '%s'\n", argv[1]); + return 1; + } + + struct sshnc_client *client; + enum sshnc_retval ret = sshnc_connect( + server_host, port, "tomsg", "tomsg", hostkey_checker, NULL, &client); + + if (ret != SSHNC_OK) { + fprintf(stderr, "Could not connect: %s\n", sshnc_strerror(ret)); + return 1; + } + + struct pollfd polls[2]; + polls[0] = (struct pollfd){ + .fd = sshnc_poll_fd(client), + .events = POLLIN, + }; + polls[1] = (struct pollfd){ + .fd = STDIN_FILENO, + .events = POLLIN, + }; + + while (true) { + int pollret = poll(polls, sizeof polls / sizeof polls[0], -1); + if (pollret < 0) { + perror("poll"); + goto cleanup; + } + + if (polls[0].revents & (POLLERR | POLLNVAL)) { + fprintf(stderr, "Error reading from SSH socket\n"); + goto cleanup; + } + if (polls[1].revents & (POLLERR | POLLNVAL)) { + fprintf(stderr, "Error reading from stdin\n"); + goto cleanup; + } + + if (polls[0].revents & (POLLIN | POLLHUP)) { + char buffer[4096]; + size_t length = 0; + ret = sshnc_maybe_recv(client, sizeof buffer, buffer, &length); + if (ret == SSHNC_OK) { + fwrite(buffer, 1, length, stdout); + } else if (ret == SSHNC_EOF) { + break; + } else if (ret != SSHNC_AGAIN) { + fprintf(stderr, "Error on SSH recv: %s\n", sshnc_strerror(ret)); + goto cleanup; + } + } + + if (polls[1].revents & (POLLIN | POLLHUP)) { + char buffer[4096]; + ssize_t nr = read(STDIN_FILENO, buffer, sizeof buffer); + if (nr < 0) { + perror("Error reading from stdin"); + goto cleanup; + } + if (nr == 0) { + break; + } + + ret = sshnc_send(client, buffer, nr); + if (ret == SSHNC_EOF) { + break; + } else if (ret != SSHNC_OK) { + fprintf(stderr, "Error on SSH send: %s\n", sshnc_strerror(ret)); + goto cleanup; + } + } + } + + sshnc_close(client); + return 0; + +cleanup: + sshnc_close(client); + return 1; +} -- cgit v1.2.3-70-g09d2