From 05a818bb65d00ef89cf97e59ebca867fcef5863a Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Thu, 9 Jul 2020 18:30:13 +0200 Subject: ssh: Abstract SSH communication in mini-library --- ssh/client.c | 295 +++++++++++++++-------------------------------------------- 1 file changed, 74 insertions(+), 221 deletions(-) (limited to 'ssh/client.c') diff --git a/ssh/client.c b/ssh/client.c index fc4ad96..b9bd52a 100644 --- a/ssh/client.c +++ b/ssh/client.c @@ -3,12 +3,10 @@ #include #include #include -#include -#include -#include -#include #include +#include #include "util.h" +#include "sshnc.h" static bool prompt_yn(const char *text) { @@ -47,86 +45,17 @@ static bool prompt_yn(const char *text) { return response; } -struct session_data { - ssh_session session; - ssh_channel channel; - bool should_close; -}; +static bool hostkey_checker(const unsigned char *hash, size_t length) { + printf("Server host key hash: %s\n", sshnc_print_hash(hash, length)); -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; + 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 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; + return response; } int main(int argc, char **argv) { @@ -144,156 +73,80 @@ int main(int argc, char **argv) { return 1; } - 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 = true; - 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"); - -retry_userauth: - 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: - if (ssh_get_status(session) & (SSH_CLOSED | SSH_CLOSED_ERROR)) { - fprintf(stderr, "Socket unexpectedly closed!\n"); - return 1; - } - goto retry_userauth; - } - - 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; - } + struct sshnc_client *client; + enum sshnc_retval ret = sshnc_connect( + server_host, port, "tomsg", "tomsg", hostkey_checker, &client); - 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(sizeof(struct session_data)); - if (!sesdata) { - fprintf(stderr, "Out of memory (allocating session_data)!\n"); + if (ret != SSHNC_OK) { + fprintf(stderr, "Could not connect: %s\n", sshnc_strerror(ret)); return 1; } - sesdata->session = session; - sesdata->channel = channel; - sesdata->should_close = false; + struct pollfd polls[2]; + polls[0] = (struct pollfd){ + .fd = sshnc_poll_fd(client), + .events = POLLIN, + }; + polls[1] = (struct pollfd){ + .fd = STDIN_FILENO, + .events = POLLIN, + }; - 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; - } + while (true) { + int pollret = poll(polls, sizeof polls / sizeof polls[0], -1); + if (pollret < 0) { + perror("poll"); + goto cleanup; + } - printf("Set callbacks\n"); + 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; + } - 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; - } + 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; + } + } - printf("Created event object\n"); + 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; + } - 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"); + 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; + } } } -cleanup_connected: - ssh_disconnect(session); -cleanup_unconnected: - ssh_free(session); + sshnc_close(client); + return 0; + +cleanup: + sshnc_close(client); + return 1; } -- cgit v1.2.3-54-g00ecf