aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ssh/.gitignore1
-rw-r--r--ssh/Makefile5
-rw-r--r--ssh/client.c305
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);
+}