diff options
-rw-r--r-- | ssh/.gitignore | 1 | ||||
-rw-r--r-- | ssh/Makefile | 5 | ||||
-rw-r--r-- | ssh/client.c | 305 |
3 files changed, 310 insertions, 1 deletions
diff --git a/ssh/.gitignore b/ssh/.gitignore index 2cb4873..36f3e53 100644 --- a/ssh/.gitignore +++ b/ssh/.gitignore @@ -1,5 +1,6 @@ host_key host_key.pub server +client *.o compile_commands.json diff --git a/ssh/Makefile b/ssh/Makefile index 63cb462..b7c1d1b 100644 --- a/ssh/Makefile +++ b/ssh/Makefile @@ -6,7 +6,7 @@ LDFLAGS += $(shell pkg-config --libs libssh) .PHONY: all clean -all: server +all: server client clean: rm -f server *.o @@ -15,5 +15,8 @@ clean: server: server.o ../global.o ../memory.o $(CC) -o $@ $^ $(LDFLAGS) +client: client.o ../global.o ../memory.o + $(CC) -o $@ $^ $(LDFLAGS) + %.o: %.c $(wildcard *.h) $(CC) $(CFLAGS) -c -o $@ $< diff --git a/ssh/client.c b/ssh/client.c new file mode 100644 index 0000000..9e60033 --- /dev/null +++ b/ssh/client.c @@ -0,0 +1,305 @@ +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <libssh/callbacks.h> +#include <sys/select.h> +#include <poll.h> +#include "../global.h" + + +static void parse_args(const char *arg, const char **server_host, int *port) { + const char *ptr = strchr(arg, ':'); + if (ptr == NULL) { + *server_host = arg; + } else { + size_t length = ptr - arg; + char *host = malloc(length + 1, char); + memcpy(host, arg, length); + host[length] = '\0'; + *server_host = host; + + char *endp; + *port = strtol(ptr + 1, &endp, 10); + if (endp == ptr || *endp != '\0') { + fprintf(stderr, "Cannot parse server:port from argument '%s'\n", arg); + exit(1); + } + } +} + +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; +} + +struct session_data { + ssh_session session; + ssh_channel channel; + bool should_close; +}; + +void channel_close_cb(ssh_session session, ssh_channel channel, void *sesdata_) { + (void)session; (void)channel; + struct session_data *sesdata = (struct session_data*)sesdata_; + sesdata->should_close = true; +} + +void channel_eof_cb(ssh_session session, ssh_channel channel, void *sesdata_) { + (void)session; (void)channel; + struct session_data *sesdata = (struct session_data*)sesdata_; + sesdata->should_close = true; +} + +int channel_data_cb(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *sesdata_) { + (void)session; (void)channel; (void)is_stderr; + struct session_data *sesdata = (struct session_data*)sesdata_; + + const char *start = (const char*)data; + const char *cursor = start; + const char *end = cursor + len; + + while (cursor < end) { + ssize_t nw = write(STDOUT_FILENO, cursor, end - cursor); + if (nw < 0) { + if (errno == EINTR) continue; + perror("write(stdout)"); + sesdata->should_close = true; + return cursor - start; + } + assert(nw > 0); + cursor += nw; + } + + return len; +} + +int channel_write_wontblock_cb(ssh_session session, ssh_channel channel, size_t bytes, void *sesdata_) { + (void)session; (void)channel; (void)sesdata_; + fprintf(stderr, "(write won't block for %zu bytes)\n", bytes); + return 0; +} + +int stdin_data_cb(int fd, int revents, void *sesdata_) { + (void)fd; + struct session_data *sesdata = (struct session_data*)sesdata_; + + if (revents & POLLIN) { + char buffer[1024]; + ssize_t nr = read(STDIN_FILENO, buffer, sizeof buffer); + if (nr < 0) { + if (errno == EINTR) return 0; + perror("read(stdin)"); + sesdata->should_close = true; + return 0; + } + + if (nr == 0) { // eof + sesdata->should_close = true; + return 0; + } + + const char *cursor = buffer; + while (cursor < buffer + nr) { + int ret = ssh_channel_write(sesdata->channel, cursor, buffer + nr - cursor); + if (ret == SSH_ERROR) { + fprintf(stderr, "Error writing to channel: %s\n", + ssh_get_error(sesdata->channel)); + sesdata->should_close = true; + return 0; + } + assert(ret > 0); + cursor += ret; + } + } + return 0; +} + +int main(int argc, char **argv) { + const char *server_host = NULL; + int port = 2222; + + if (argc != 2) { + fprintf(stderr, "Usage: %s <server[:port]>\n", argv[0]); + fprintf(stderr, "If port is not specified, %d is assumed.\n", port); + return 1; + } + + parse_args(argv[1], &server_host, &port); + + ssh_session session = ssh_new(); + if (!session) { + fprintf(stderr, "Could not open SSH session\n"); + goto cleanup_unconnected; + } + + const char *ciphers_str = "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc"; + bool procconfig = false; + bool ok = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &procconfig) == SSH_OK; + ok &= ssh_options_set(session, SSH_OPTIONS_USER, "tomsg") == SSH_OK; + ok &= ssh_options_set(session, SSH_OPTIONS_HOST, server_host) == SSH_OK; + ok &= ssh_options_set(session, SSH_OPTIONS_PORT, &port) == SSH_OK; + ok &= ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, ciphers_str) == SSH_OK; + ok &= ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, ciphers_str) == SSH_OK; + // int loglevel = SSH_LOG_PROTOCOL; + // ok &= ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &loglevel) == SSH_OK; + + if (!ok) { + fprintf(stderr, "Could not set options on SSH session: %s\n", ssh_get_error(session)); + goto cleanup_unconnected; + } + + if (ssh_connect(session) != SSH_OK) { + fprintf(stderr, "Could not connect to %s:%d: %s\n", + server_host, port, ssh_get_error(session)); + goto cleanup_unconnected; + } + + ssh_key host_key; + if (ssh_get_server_publickey(session, &host_key) != SSH_OK) { + fprintf(stderr, "Could not get host key from session: %s\n", ssh_get_error(session)); + goto cleanup_connected; + } + + unsigned char *host_key_hash = NULL; + size_t host_key_hash_length = 0; + 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"); + goto cleanup_connected; + } + + printf("Server host key hash: "); + fflush(stdout); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, host_key_hash, host_key_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"); + goto cleanup_connected; + } + + printf("Connected.\n"); + + switch (ssh_userauth_none(session, NULL)) { + case SSH_AUTH_ERROR: + fprintf(stderr, "Error authenticating: %s\n", ssh_get_error(session)); + return 1; + + case SSH_AUTH_DENIED: + case SSH_AUTH_PARTIAL: + fprintf(stderr, "Server denied authentication.\n"); + return 1; + + case SSH_AUTH_SUCCESS: + break; + + case SSH_AUTH_AGAIN: + assert(false); + } + + printf("Authenticated.\n"); + + ssh_channel channel = ssh_channel_new(session); + if (!channel) { + fprintf(stderr, "Failed to allocate channel: %s\n", ssh_get_error(session)); + goto cleanup_connected; + } + + printf("Created channel\n"); + + if (ssh_channel_open_session(channel) != SSH_OK) { + fprintf(stderr, "Failed to open channel: %s\n", ssh_get_error(channel)); + goto cleanup_connected; + } + + printf("Opened channel\n"); + + if (ssh_channel_request_subsystem(channel, "tomsg") != SSH_OK) { + fprintf(stderr, "Server did not allow opening 'tomsg' channel: %s\n", ssh_get_error(channel)); + goto cleanup_connected; + } + + printf("Obtained tomsg subsystem on channel\n"); + + struct session_data *sesdata = malloc(1, struct session_data); + sesdata->session = session; + sesdata->channel = channel; + sesdata->should_close = false; + + struct ssh_channel_callbacks_struct chan_cb; + memset(&chan_cb, 0, sizeof chan_cb); + ssh_callbacks_init(&chan_cb); + chan_cb.userdata = sesdata; + chan_cb.channel_close_function = channel_close_cb; + chan_cb.channel_eof_function = channel_eof_cb; + chan_cb.channel_data_function = channel_data_cb; + chan_cb.channel_write_wontblock_function = channel_write_wontblock_cb; + + if (ssh_set_channel_callbacks(channel, &chan_cb) != SSH_OK) { + fprintf(stderr, "Failed to set channel callbacks\n"); + goto cleanup_connected; + } + + printf("Set callbacks\n"); + + ssh_event event = ssh_event_new(); + if (!event + || ssh_event_add_session(event, session) != SSH_OK + || ssh_event_add_fd(event, STDIN_FILENO, POLLIN, stdin_data_cb, sesdata) != SSH_OK) { + fprintf(stderr, "Failed to create ssh event context\n"); + goto cleanup_connected; + } + + printf("Created event object\n"); + + while (!sesdata->should_close) { + // printf("poll loop...\n"); + ssh_event_dopoll(event, -1); + int status = ssh_get_status(session); + if (status & (SSH_CLOSED | SSH_CLOSED_ERROR)) goto cleanup_connected; + if (status & SSH_READ_PENDING) { + printf("read pending?\n"); + } + } + +cleanup_connected: + ssh_disconnect(session); +cleanup_unconnected: + ssh_free(session); +} |