From 075b7ffba9b39d0e9dabe5f99518489ac74003c5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 29 Feb 2016 21:51:42 -0800 Subject: [PATCH 1/4] GUAC-1389: Add screen sharing support to SSH. --- Makefile.am | 2 +- src/protocols/ssh/Makefile.am | 12 +- src/protocols/ssh/client.c | 288 ++++-------------- src/protocols/ssh/client.h | 144 +-------- src/protocols/ssh/clipboard.c | 19 +- src/protocols/ssh/clipboard.h | 6 +- src/protocols/ssh/guac_handlers.c | 128 -------- src/protocols/ssh/input.c | 90 ++++++ .../ssh/{guac_handlers.h => input.h} | 28 +- src/protocols/ssh/settings.c | 269 ++++++++++++++++ src/protocols/ssh/settings.h | 199 ++++++++++++ src/protocols/ssh/sftp.c | 59 +++- src/protocols/ssh/sftp.h | 9 +- src/protocols/ssh/{ssh_client.c => ssh.c} | 169 +++++----- src/protocols/ssh/ssh.h | 107 +++++++ src/protocols/ssh/user.c | 87 ++++++ src/protocols/ssh/{ssh_client.h => user.h} | 13 +- 17 files changed, 1020 insertions(+), 609 deletions(-) delete mode 100644 src/protocols/ssh/guac_handlers.c create mode 100644 src/protocols/ssh/input.c rename src/protocols/ssh/{guac_handlers.h => input.h} (69%) create mode 100644 src/protocols/ssh/settings.c create mode 100644 src/protocols/ssh/settings.h rename src/protocols/ssh/{ssh_client.c => ssh.c} (61%) create mode 100644 src/protocols/ssh/ssh.h create mode 100644 src/protocols/ssh/user.c rename src/protocols/ssh/{ssh_client.h => user.h} (83%) diff --git a/Makefile.am b/Makefile.am index 562d712a..ec52ed37 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,7 +54,7 @@ SUBDIRS += src/protocols/rdp endif if ENABLE_SSH -#SUBDIRS += src/protocols/ssh +SUBDIRS += src/protocols/ssh endif if ENABLE_TELNET diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index ad1a2f56..1587e61c 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -28,16 +28,20 @@ lib_LTLIBRARIES = libguac-client-ssh.la libguac_client_ssh_la_SOURCES = \ client.c \ clipboard.c \ - guac_handlers.c \ + input.c \ + settings.c \ sftp.c \ - ssh_client.c + ssh.c \ + user.c noinst_HEADERS = \ client.h \ clipboard.h \ - guac_handlers.h \ + input.h \ + settings.h \ sftp.h \ - ssh_client.h + ssh.h \ + user.h # Add agent sources if enabled if ENABLE_SSH_AGENT diff --git a/src/protocols/ssh/client.c b/src/protocols/ssh/client.c index 5e9edaaa..e597147e 100644 --- a/src/protocols/ssh/client.c +++ b/src/protocols/ssh/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2016 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,246 +23,37 @@ #include "config.h" #include "client.h" -#include "clipboard.h" -#include "guac_handlers.h" -#include "ssh_client.h" +#include "guac_sftp.h" +#include "ssh.h" #include "terminal.h" +#include "user.h" #include #include -#include #include #include #include -#include -#include -#define GUAC_SSH_DEFAULT_FONT_NAME "monospace" -#define GUAC_SSH_DEFAULT_FONT_SIZE 12 -#define GUAC_SSH_DEFAULT_PORT "22" +int guac_client_init(guac_client* client) { -/* Client plugin arguments */ -const char* GUAC_CLIENT_ARGS[] = { - "hostname", - "port", - "username", - "password", - "font-name", - "font-size", - "enable-sftp", - "private-key", - "passphrase", -#ifdef ENABLE_SSH_AGENT - "enable-agent", -#endif - "color-scheme", - "command", - "typescript-path", - "typescript-name", - "create-typescript-path", - NULL -}; + /* Set client args */ + client->args = GUAC_SSH_CLIENT_ARGS; -enum __SSH_ARGS_IDX { + /* Allocate client instance data */ + guac_ssh_client* ssh_client = calloc(1, sizeof(guac_ssh_client)); + client->data = ssh_client; - /** - * The hostname to connect to. Required. - */ - IDX_HOSTNAME, - - /** - * The port to connect to. Optional. - */ - IDX_PORT, - - /** - * The name of the user to login as. Optional. - */ - IDX_USERNAME, - - /** - * The password to use when logging in. Optional. - */ - IDX_PASSWORD, - - /** - * The name of the font to use within the terminal. - */ - IDX_FONT_NAME, - - /** - * The size of the font to use within the terminal, in points. - */ - IDX_FONT_SIZE, - - /** - * Whether SFTP should be enabled. - */ - IDX_ENABLE_SFTP, - - /** - * The private key to use for authentication, if any. - */ - IDX_PRIVATE_KEY, - - /** - * The passphrase required to decrypt the private key, if any. - */ - IDX_PASSPHRASE, - -#ifdef ENABLE_SSH_AGENT - /** - * Whether SSH agent forwarding support should be enabled. - */ - IDX_ENABLE_AGENT, -#endif - - /** - * The name of the color scheme to use. Currently valid color schemes are: - * "black-white", "white-black", "gray-black", and "green-black", each - * following the "foreground-background" pattern. By default, this will be - * "gray-black". - */ - IDX_COLOR_SCHEME, - - /** - * The command to run instead if the default shell. If omitted, a normal - * shell session will be created. - */ - IDX_COMMAND, - - /** - * The full absolute path to the directory in which typescripts should be - * written. - */ - IDX_TYPESCRIPT_PATH, - - /** - * The name that should be given to typescripts which are written in the - * given path. Each typescript will consist of two files: "NAME" and - * "NAME.timing". - */ - IDX_TYPESCRIPT_NAME, - - /** - * Whether the specified typescript path should automatically be created - * if it does not yet exist. - */ - IDX_CREATE_TYPESCRIPT_PATH, - - SSH_ARGS_COUNT -}; - -int guac_client_init(guac_client* client, int argc, char** argv) { - - guac_socket* socket = client->socket; - - ssh_guac_client_data* client_data = calloc(1, sizeof(ssh_guac_client_data)); - - /* Init client data */ - client->data = client_data; - - if (argc != SSH_ARGS_COUNT) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong number of arguments"); - return -1; - } + /* Set handlers */ + client->join_handler = guac_ssh_user_join_handler; + client->free_handler = guac_ssh_client_free_handler; /* Set locale and warn if not UTF-8 */ setlocale(LC_CTYPE, ""); - if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) - guac_client_log(client, GUAC_LOG_INFO, "Current locale does not use UTF-8. Some characters may not render correctly."); - - /* Read parameters */ - strcpy(client_data->hostname, argv[IDX_HOSTNAME]); - strcpy(client_data->username, argv[IDX_USERNAME]); - strcpy(client_data->password, argv[IDX_PASSWORD]); - - /* Init public key auth information */ - strcpy(client_data->key_base64, argv[IDX_PRIVATE_KEY]); - strcpy(client_data->key_passphrase, argv[IDX_PASSPHRASE]); - - /* Read font name */ - if (argv[IDX_FONT_NAME][0] != 0) - strcpy(client_data->font_name, argv[IDX_FONT_NAME]); - else - strcpy(client_data->font_name, GUAC_SSH_DEFAULT_FONT_NAME ); - - /* Read font size */ - if (argv[IDX_FONT_SIZE][0] != 0) - client_data->font_size = atoi(argv[IDX_FONT_SIZE]); - else - client_data->font_size = GUAC_SSH_DEFAULT_FONT_SIZE; - - /* Parse SFTP enable */ - client_data->enable_sftp = strcmp(argv[IDX_ENABLE_SFTP], "true") == 0; - -#ifdef ENABLE_SSH_AGENT - client_data->enable_agent = strcmp(argv[IDX_ENABLE_AGENT], "true") == 0; -#endif - - /* Read port */ - if (argv[IDX_PORT][0] != 0) - strcpy(client_data->port, argv[IDX_PORT]); - else - strcpy(client_data->port, GUAC_SSH_DEFAULT_PORT); - - /* Read command, if any */ - if (argv[IDX_COMMAND][0] != 0) - client_data->command = strdup(argv[IDX_COMMAND]); - - /* Create terminal */ - client_data->term = guac_terminal_create(client, - client_data->font_name, client_data->font_size, - client->info.optimal_resolution, - client->info.optimal_width, client->info.optimal_height, - argv[IDX_COLOR_SCHEME]); - - /* Fail if terminal init failed */ - if (client_data->term == NULL) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed"); - return -1; - } - - /* Set up typescript, if requested */ - const char* typescript_path = argv[IDX_TYPESCRIPT_PATH]; - if (typescript_path[0] != 0) { - - /* Default to "typescript" if no name provided */ - const char* typescript_name = argv[IDX_TYPESCRIPT_NAME]; - if (typescript_name[0] == 0) - typescript_name = "typescript"; - - /* Parse path creation flag */ - int create_path = - strcmp(argv[IDX_CREATE_TYPESCRIPT_PATH], "true") == 0; - - /* Create typescript */ - guac_terminal_create_typescript(client_data->term, typescript_path, - typescript_name, create_path); - - } - - /* Ensure main socket is threadsafe */ - guac_socket_require_threadsafe(socket); - - /* Send initial name */ - guac_protocol_send_name(socket, client_data->hostname); - - guac_socket_flush(socket); - - /* Set basic handlers */ - client->handle_messages = ssh_guac_client_handle_messages; - client->key_handler = ssh_guac_client_key_handler; - client->mouse_handler = ssh_guac_client_mouse_handler; - client->size_handler = ssh_guac_client_size_handler; - client->free_handler = ssh_guac_client_free_handler; - client->clipboard_handler = guac_ssh_clipboard_handler; - - /* Start client thread */ - if (pthread_create(&(client_data->client_thread), NULL, ssh_client_thread, (void*) client)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start SSH client thread"); - return -1; + if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) { + guac_client_log(client, GUAC_LOG_INFO, + "Current locale does not use UTF-8. Some characters may " + "not render correctly."); } /* Success */ @@ -270,3 +61,48 @@ int guac_client_init(guac_client* client, int argc, char** argv) { } +int guac_ssh_client_free_handler(guac_client* client) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Close SSH channel */ + if (ssh_client->term_channel != NULL) { + libssh2_channel_send_eof(ssh_client->term_channel); + libssh2_channel_close(ssh_client->term_channel); + } + + /* Free terminal (which may still be using term_channel) */ + if (ssh_client->term != NULL) { + guac_terminal_free(ssh_client->term); + pthread_join(ssh_client->client_thread, NULL); + } + + /* Free terminal channel now that the terminal is finished */ + if (ssh_client->term_channel != NULL) + libssh2_channel_free(ssh_client->term_channel); + + /* Clean up the SFTP filesystem object and session */ + if (ssh_client->sftp_filesystem) { + guac_common_ssh_destroy_sftp_filesystem(ssh_client->sftp_filesystem); + guac_common_ssh_destroy_session(ssh_client->sftp_session); + } + + /* Free interactive SSH session */ + if (ssh_client->session != NULL) + guac_common_ssh_destroy_session(ssh_client->session); + + /* Free SSH client credentials */ + if (ssh_client->user != NULL) + guac_common_ssh_destroy_user(ssh_client->user); + + /* Free parsed settings */ + if (ssh_client->settings != NULL) + guac_ssh_settings_free(ssh_client->settings); + + /* Free client structure */ + free(ssh_client); + + guac_common_ssh_uninit(); + return 0; +} + diff --git a/src/protocols/ssh/client.h b/src/protocols/ssh/client.h index 32e297c4..a9c1c17a 100644 --- a/src/protocols/ssh/client.h +++ b/src/protocols/ssh/client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2016 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,138 +20,24 @@ * THE SOFTWARE. */ +#ifndef GUAC_SSH_CLIENT_H +#define GUAC_SSH_CLIENT_H -#ifndef _SSH_GUAC_CLIENT_H -#define _SSH_GUAC_CLIENT_H - -#include "config.h" - -#include "guac_ssh.h" -#include "guac_ssh_user.h" -#include "sftp.h" -#include "terminal.h" - -#include -#include - -#include - -#ifdef ENABLE_SSH_AGENT -#include "ssh_agent.h" -#endif - -#include -#include +#include /** - * SSH-specific client data. + * Handler which is invoked when the SSH client needs to be disconnected (if + * connected) and freed. This can happen if initialization fails, or all users + * have left the connection. + * + * @param client + * The client associated with the SSH client to be freed. + * + * @return + * Zero on success, non-zero if an error occurs preventing the client from + * being entirely freed. */ -typedef struct ssh_guac_client_data { - - /** - * The hostname of the SSH server to connect to. - */ - char hostname[1024]; - - /** - * The port of the SSH server to connect to. - */ - char port[64]; - - /** - * The name of the user to login as. - */ - char username[1024]; - - /** - * The password to give when authenticating. - */ - char password[1024]; - - /** - * The private key, encoded as base64. - */ - char key_base64[4096]; - - /** - * The password to use to decrypt the given private key. - */ - char key_passphrase[1024]; - - /** - * The command to run instead of the default shell. If a normal shell - * session is desired, this will be NULL. - */ - char* command; - - /** - * The name of the font to use for display rendering. - */ - char font_name[1024]; - - /** - * The size of the font to use, in points. - */ - int font_size; - - /** - * Whether SFTP is enabled. - */ - bool enable_sftp; - -#ifdef ENABLE_SSH_AGENT - /** - * Whether the SSH agent is enabled. - */ - bool enable_agent; - - /** - * The current agent, if any. - */ - ssh_auth_agent* auth_agent; -#endif - - /** - * The SSH client thread. - */ - pthread_t client_thread; - - /** - * The user and credentials to use for all SSH sessions. - */ - guac_common_ssh_user* user; - - /** - * SSH session, used by the SSH client thread. - */ - guac_common_ssh_session* session; - - /** - * SFTP session, used by the SFTP client/filesystem. - */ - guac_common_ssh_session* sftp_session; - - /** - * The filesystem object exposed for the SFTP session. - */ - guac_object* sftp_filesystem; - - /** - * SSH terminal channel, used by the SSH client thread. - */ - LIBSSH2_CHANNEL* term_channel; - - /** - * Lock dictating access to the SSH terminal channel. - */ - pthread_mutex_t term_channel_lock; - - /** - * The terminal which will render all output from the SSH client. - */ - guac_terminal* term; - -} ssh_guac_client_data; +int guac_ssh_client_free_handler(guac_client* client); #endif diff --git a/src/protocols/ssh/clipboard.c b/src/protocols/ssh/clipboard.c index 34a86fe7..2f5b707c 100644 --- a/src/protocols/ssh/clipboard.c +++ b/src/protocols/ssh/clipboard.c @@ -21,19 +21,21 @@ */ #include "config.h" -#include "client.h" #include "clipboard.h" +#include "ssh.h" #include "terminal.h" #include #include +#include -int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype) { /* Clear clipboard and prepare for new data */ - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_terminal_clipboard_reset(client_data->term, mimetype); + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal_clipboard_reset(ssh_client->term, mimetype); /* Set handlers for clipboard stream */ stream->blob_handler = guac_ssh_clipboard_blob_handler; @@ -42,17 +44,18 @@ int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream, return 0; } -int guac_ssh_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { /* Append new data */ - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_terminal_clipboard_append(client_data->term, data, length); + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal_clipboard_append(ssh_client->term, data, length); return 0; } -int guac_ssh_clipboard_end_handler(guac_client* client, guac_stream* stream) { +int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream) { /* Nothing to do - clipboard is implemented within client */ diff --git a/src/protocols/ssh/clipboard.h b/src/protocols/ssh/clipboard.h index dfa2bd6e..0d6a4d84 100644 --- a/src/protocols/ssh/clipboard.h +++ b/src/protocols/ssh/clipboard.h @@ -31,19 +31,19 @@ /** * Handler for inbound clipboard data. */ -int guac_ssh_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype); /** * Handler for stream data related to clipboard. */ -int guac_ssh_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length); /** * Handler for end-of-stream related to clipboard. */ -int guac_ssh_clipboard_end_handler(guac_client* client, guac_stream* stream); +int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream); #endif diff --git a/src/protocols/ssh/guac_handlers.c b/src/protocols/ssh/guac_handlers.c deleted file mode 100644 index 3b1ce2ce..00000000 --- a/src/protocols/ssh/guac_handlers.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2013 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "config.h" - -#include "client.h" -#include "guac_handlers.h" -#include "guac_sftp.h" -#include "guac_ssh.h" -#include "terminal.h" - -#include -#include -#include -#include - -#include -#include - -int ssh_guac_client_handle_messages(guac_client* client) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - return guac_terminal_render_frame(client_data->term); - -} - -int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_terminal* term = client_data->term; - - /* Send mouse event */ - guac_terminal_send_mouse(term, x, y, mask); - return 0; - -} - -int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed) { - - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_terminal* term = client_data->term; - - /* Send key */ - guac_terminal_send_key(term, keysym, pressed); - return 0; - -} - -int ssh_guac_client_size_handler(guac_client* client, int width, int height) { - - /* Get terminal */ - ssh_guac_client_data* guac_client_data = (ssh_guac_client_data*) client->data; - guac_terminal* terminal = guac_client_data->term; - - /* Resize terminal */ - guac_terminal_resize(terminal, width, height); - - /* Update SSH pty size if connected */ - if (guac_client_data->term_channel != NULL) { - pthread_mutex_lock(&(guac_client_data->term_channel_lock)); - libssh2_channel_request_pty_size(guac_client_data->term_channel, - terminal->term_width, terminal->term_height); - pthread_mutex_unlock(&(guac_client_data->term_channel_lock)); - } - - return 0; -} - -int ssh_guac_client_free_handler(guac_client* client) { - - ssh_guac_client_data* guac_client_data = (ssh_guac_client_data*) client->data; - - /* Close SSH channel */ - if (guac_client_data->term_channel != NULL) { - libssh2_channel_send_eof(guac_client_data->term_channel); - libssh2_channel_close(guac_client_data->term_channel); - } - - /* Free terminal */ - guac_terminal_free(guac_client_data->term); - pthread_join(guac_client_data->client_thread, NULL); - - /* Free channels */ - libssh2_channel_free(guac_client_data->term_channel); - - /* Clean up the SFTP filesystem object and session */ - if (guac_client_data->sftp_filesystem) { - guac_common_ssh_destroy_sftp_filesystem(guac_client_data->sftp_filesystem); - guac_common_ssh_destroy_session(guac_client_data->sftp_session); - } - - /* Free session */ - if (guac_client_data->session != NULL) - guac_common_ssh_destroy_session(guac_client_data->session); - - /* Free user */ - if (guac_client_data->user != NULL) - guac_common_ssh_destroy_user(guac_client_data->user); - - /* Free copied settings */ - free(guac_client_data->command); - - /* Free generic data struct */ - free(client->data); - - guac_common_ssh_uninit(); - return 0; -} - diff --git a/src/protocols/ssh/input.c b/src/protocols/ssh/input.c new file mode 100644 index 00000000..ee559bef --- /dev/null +++ b/src/protocols/ssh/input.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "guac_cursor.h" +#include "guac_display.h" +#include "ssh.h" +#include "terminal.h" + +#include +#include +#include + +#include + +int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal* term = ssh_client->term; + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Send mouse event */ + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; +} + +int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal* term = ssh_client->term; + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Send key */ + guac_terminal_send_key(term, keysym, pressed); + return 0; +} + +int guac_ssh_user_size_handler(guac_user* user, int width, int height) { + + /* Get terminal */ + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal* terminal = ssh_client->term; + + /* Skip if terminal not yet ready */ + if (terminal == NULL) + return 0; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + /* Update SSH pty size if connected */ + if (ssh_client->term_channel != NULL) { + pthread_mutex_lock(&(ssh_client->term_channel_lock)); + libssh2_channel_request_pty_size(ssh_client->term_channel, + terminal->term_width, terminal->term_height); + pthread_mutex_unlock(&(ssh_client->term_channel_lock)); + } + + return 0; +} + diff --git a/src/protocols/ssh/guac_handlers.h b/src/protocols/ssh/input.h similarity index 69% rename from src/protocols/ssh/guac_handlers.h rename to src/protocols/ssh/input.h index 917b59a6..98607360 100644 --- a/src/protocols/ssh/guac_handlers.h +++ b/src/protocols/ssh/input.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2016 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,27 @@ * THE SOFTWARE. */ - -#ifndef _SSH_GUAC_HANDLERS_H -#define _SSH_GUAC_HANDLERS_H +#ifndef GUAC_SSH_INPUT_H +#define GUAC_SSH_INPUT_H #include "config.h" -#include +#include -int ssh_guac_client_handle_messages(guac_client* client); -int ssh_guac_client_key_handler(guac_client* client, int keysym, int pressed); -int ssh_guac_client_mouse_handler(guac_client* client, int x, int y, int mask); -int ssh_guac_client_size_handler(guac_client* client, int width, int height); -int ssh_guac_client_free_handler(guac_client* client); +/** + * Handler for Guacamole user mouse events. + */ +int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask); + +/** + * Handler for Guacamole user key events. + */ +int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed); + +/** + * Handler for Guacamole user size events. + */ +int guac_ssh_user_size_handler(guac_user* user, int width, int height); #endif diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c new file mode 100644 index 00000000..c3f95b94 --- /dev/null +++ b/src/protocols/ssh/settings.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "client.h" +#include "settings.h" + +#include + +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_SSH_CLIENT_ARGS[] = { + "hostname", + "port", + "username", + "password", + "font-name", + "font-size", + "enable-sftp", + "private-key", + "passphrase", +#ifdef ENABLE_SSH_AGENT + "enable-agent", +#endif + "color-scheme", + "command", + "typescript-path", + "typescript-name", + "create-typescript-path", + NULL +}; + +enum SSH_ARGS_IDX { + + /** + * The hostname to connect to. Required. + */ + IDX_HOSTNAME, + + /** + * The port to connect to. Optional. + */ + IDX_PORT, + + /** + * The name of the user to login as. Optional. + */ + IDX_USERNAME, + + /** + * The password to use when logging in. Optional. + */ + IDX_PASSWORD, + + /** + * The name of the font to use within the terminal. + */ + IDX_FONT_NAME, + + /** + * The size of the font to use within the terminal, in points. + */ + IDX_FONT_SIZE, + + /** + * Whether SFTP should be enabled. + */ + IDX_ENABLE_SFTP, + + /** + * The private key to use for authentication, if any. + */ + IDX_PRIVATE_KEY, + + /** + * The passphrase required to decrypt the private key, if any. + */ + IDX_PASSPHRASE, + +#ifdef ENABLE_SSH_AGENT + /** + * Whether SSH agent forwarding support should be enabled. + */ + IDX_ENABLE_AGENT, +#endif + + /** + * The name of the color scheme to use. Currently valid color schemes are: + * "black-white", "white-black", "gray-black", and "green-black", each + * following the "foreground-background" pattern. By default, this will be + * "gray-black". + */ + IDX_COLOR_SCHEME, + + /** + * The command to run instead if the default shell. If omitted, a normal + * shell session will be created. + */ + IDX_COMMAND, + + /** + * The full absolute path to the directory in which typescripts should be + * written. + */ + IDX_TYPESCRIPT_PATH, + + /** + * The name that should be given to typescripts which are written in the + * given path. Each typescript will consist of two files: "NAME" and + * "NAME.timing". + */ + IDX_TYPESCRIPT_NAME, + + /** + * Whether the specified typescript path should automatically be created + * if it does not yet exist. + */ + IDX_CREATE_TYPESCRIPT_PATH, + + SSH_ARGS_COUNT +}; + +guac_ssh_settings* guac_ssh_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != SSH_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + SSH_ARGS_COUNT, argc); + return NULL; + } + + guac_ssh_settings* settings = calloc(1, sizeof(guac_ssh_settings)); + + /* Read parameters */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + settings->username = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_USERNAME, NULL); + + settings->password = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_PASSWORD, NULL); + + /* Init public key auth information */ + settings->key_base64 = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_PRIVATE_KEY, NULL); + + settings->key_passphrase = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_PASSPHRASE, NULL); + + /* Read font name */ + settings->font_name = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_FONT_NAME, GUAC_SSH_DEFAULT_FONT_NAME); + + /* Read font size */ + settings->font_size = + guac_user_parse_args_int(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_FONT_SIZE, GUAC_SSH_DEFAULT_FONT_SIZE); + + /* Copy requested color scheme */ + settings->color_scheme = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_COLOR_SCHEME, ""); + + /* Pull width/height/resolution directly from user */ + settings->width = user->info.optimal_width; + settings->height = user->info.optimal_height; + settings->resolution = user->info.optimal_resolution; + + /* Parse SFTP enable */ + settings->enable_sftp = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_ENABLE_SFTP, false); + +#ifdef ENABLE_SSH_AGENT + settings->enable_agent = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_ENABLE_AGENT, false); +#endif + + /* Read port */ + settings->port = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_PORT, GUAC_SSH_DEFAULT_PORT); + + /* Read command, if any */ + settings->command = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_COMMAND, NULL); + + /* Read typescript path */ + settings->typescript_path = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_PATH, NULL); + + /* Read typescript name */ + settings->typescript_name = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_NAME, GUAC_SSH_DEFAULT_TYPESCRIPT_NAME); + + /* Parse path creation flag */ + settings->create_typescript_path = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_CREATE_TYPESCRIPT_PATH, false); + + /* Parsing was successful */ + return settings; + +} + +void guac_ssh_settings_free(guac_ssh_settings* settings) { + + /* Free network connection information */ + free(settings->hostname); + free(settings->port); + + /* Free credentials */ + free(settings->username); + free(settings->password); + free(settings->key_base64); + free(settings->key_passphrase); + + /* Free display preferences */ + free(settings->font_name); + free(settings->color_scheme); + + /* Free requested command */ + free(settings->command); + + /* Free typescript settings */ + free(settings->typescript_name); + free(settings->typescript_path); + + /* Free overall structure */ + free(settings); + +} + diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h new file mode 100644 index 00000000..9ab2d4ac --- /dev/null +++ b/src/protocols/ssh/settings.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2016 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_SSH_SETTINGS_H +#define GUAC_SSH_SETTINGS_H + +#include "config.h" + +#include + +#include + +/** + * The name of the font to use for the terminal if no name is specified. + */ +#define GUAC_SSH_DEFAULT_FONT_NAME "monospace" + +/** + * The size of the font to use for the terminal if no font size is specified, + * in points. + */ +#define GUAC_SSH_DEFAULT_FONT_SIZE 12 + +/** + * The port to connect to when initiating any SSH connection, if no other port + * is specified. + */ +#define GUAC_SSH_DEFAULT_PORT "22" + +/** + * The filename to use for the typescript, if not specified. + */ +#define GUAC_SSH_DEFAULT_TYPESCRIPT_NAME "typescript" + +/** + * Settings for the SSH connection. The values for this structure are parsed + * from the arguments given during the Guacamole protocol handshake using the + * guac_ssh_parse_args() function. + */ +typedef struct guac_ssh_settings { + + /** + * The hostname of the SSH server to connect to. + */ + char* hostname; + + /** + * The port of the SSH server to connect to. + */ + char* port; + + /** + * The name of the user to login as, if any. If no username is specified, + * this will be NULL. + */ + char* username; + + /** + * The password to give when authenticating, if any. If no password is + * specified, this will be NULL. + */ + char* password; + + /** + * The private key, encoded as base64, if any. If no private key is + * specified, this will be NULL. + */ + char* key_base64; + + /** + * The passphrase to use to decrypt the given private key, if any. If no + * passphrase is specified, this will be NULL. + */ + char* key_passphrase; + + /** + * The command to run instead of the default shell. If a normal shell + * session is desired, this will be NULL. + */ + char* command; + + /** + * The name of the font to use for display rendering. + */ + char* font_name; + + /** + * The size of the font to use, in points. + */ + int font_size; + + /** + * The name of the color scheme to use. + */ + char* color_scheme; + + /** + * The desired width of the terminal display, in pixels. + */ + int width; + + /** + * The desired height of the terminal display, in pixels. + */ + int height; + + /** + * The desired screen resolution, in DPI. + */ + int resolution; + + /** + * Whether SFTP is enabled. + */ + bool enable_sftp; + +#ifdef ENABLE_SSH_AGENT + /** + * Whether the SSH agent is enabled. + */ + bool enable_agent; +#endif + + /** + * The path in which the typescript should be saved, if enabled. If no + * typescript should be saved, this will be NULL. + */ + char* typescript_path; + + /** + * The filename to use for the typescript, if enabled. + */ + char* typescript_name; + + /** + * Whether the typescript path should be automatically created if it does + * not already exist. + */ + bool create_typescript_path; + +} guac_ssh_settings; + +/** + * Parses all given args, storing them in a newly-allocated settings object. If + * the args fail to parse, NULL is returned. + * + * @param user + * The user who submitted the given arguments while joining the + * connection. + * + * @param argc + * The number of arguments within the argv array. + * + * @param argv + * The values of all arguments provided by the user. + * + * @return + * A newly-allocated settings object which must be freed with + * guac_ssh_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_ssh_settings* guac_ssh_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_ssh_settings object, having been previously allocated + * via guac_ssh_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_ssh_settings_free(guac_ssh_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_SSH_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/ssh/sftp.c b/src/protocols/ssh/sftp.c index af6be7bd..3e0b2c4d 100644 --- a/src/protocols/ssh/sftp.c +++ b/src/protocols/ssh/sftp.c @@ -22,40 +22,75 @@ #include "config.h" -#include "client.h" #include "guac_sftp.h" #include "sftp.h" +#include "ssh.h" #include #include +#include -int guac_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename) { - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_object* filesystem = client_data->sftp_filesystem; + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem; /* Handle file upload */ - return guac_common_ssh_sftp_handle_file_stream(filesystem, stream, + return guac_common_ssh_sftp_handle_file_stream(filesystem, user, stream, mimetype, filename); } -guac_stream* guac_sftp_download_file(guac_client* client, - char* filename) { +/** + * Callback invoked on the current connection owner (if any) when a file + * download is being initiated through the terminal. + * + * @param owner + * The guac_user that is the owner of the connection, or NULL if the + * connection owner has left. + * + * @param data + * The filename of the file that should be downloaded. + * + * @return + * The stream allocated for the file download, or NULL if no stream could + * be allocated. + */ +static void* guac_sftp_download_to_owner(guac_user* owner, void* data) { - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_object* filesystem = client_data->sftp_filesystem; + /* Do not bother attempting the download if the owner has left */ + if (owner == NULL) + return NULL; + + guac_client* client = owner->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem; + + /* Ignore download if filesystem has been unloaded */ + if (filesystem == NULL) + return NULL; + + char* filename = (char*) data; /* Initiate download of requested file */ - return guac_common_ssh_sftp_download_file(filesystem, filename); + return guac_common_ssh_sftp_download_file(filesystem, owner, filename); + +} + +guac_stream* guac_sftp_download_file(guac_client* client, char* filename) { + + /* Initiate download to the owner of the connection */ + return (guac_stream*) guac_client_for_owner(client, + guac_sftp_download_to_owner, filename); } void guac_sftp_set_upload_path(guac_client* client, char* path) { - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; - guac_object* filesystem = client_data->sftp_filesystem; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = ssh_client->sftp_filesystem; /* Set upload path as specified */ guac_common_ssh_sftp_set_upload_path(filesystem, path); diff --git a/src/protocols/ssh/sftp.h b/src/protocols/ssh/sftp.h index af97c98b..2beafdf8 100644 --- a/src/protocols/ssh/sftp.h +++ b/src/protocols/ssh/sftp.h @@ -28,14 +28,15 @@ #include #include +#include /** * Handles an incoming stream from a Guacamole "file" instruction, saving the * contents of that stream to the file having the given name within the * upload directory set by guac_sftp_set_upload_path(). * - * @param client - * The client receiving the uploaded file. + * @param user + * The user receiving the uploaded file. * * @param stream * The stream through which the uploaded file data will be received. @@ -52,7 +53,7 @@ * Zero if the incoming stream has been handled successfully, non-zero on * failure. */ -int guac_sftp_file_handler(guac_client* client, guac_stream* stream, +int guac_sftp_file_handler(guac_user* user, guac_stream* stream, char* mimetype, char* filename); /** @@ -62,7 +63,7 @@ int guac_sftp_file_handler(guac_client* client, guac_stream* stream, * the client. * * @param client - * The client receiving the file. + * The client associated with the terminal emulator receiving the file. * * @param filename * The filename of the file to download, relative to the given filesystem. diff --git a/src/protocols/ssh/ssh_client.c b/src/protocols/ssh/ssh.c similarity index 61% rename from src/protocols/ssh/ssh_client.c rename to src/protocols/ssh/ssh.c index ffab21ac..6b539656 100644 --- a/src/protocols/ssh/ssh_client.c +++ b/src/protocols/ssh/ssh.c @@ -22,10 +22,11 @@ #include "config.h" -#include "client.h" #include "guac_sftp.h" #include "guac_ssh.h" +#include "settings.h" #include "sftp.h" +#include "ssh.h" #include "terminal.h" #ifdef ENABLE_SSH_AGENT @@ -35,8 +36,6 @@ #include #include #include -#include -#include #include #include @@ -70,27 +69,28 @@ */ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_ssh_settings* settings = ssh_client->settings; guac_common_ssh_user* user; /* Get username */ - if (client_data->username[0] == 0) - guac_terminal_prompt(client_data->term, "Login as: ", - client_data->username, sizeof(client_data->username), true); + if (settings->username == NULL) + settings->username = guac_terminal_prompt(ssh_client->term, + "Login as: ", true); /* Create user object from username */ - user = guac_common_ssh_create_user(client_data->username); + user = guac_common_ssh_create_user(settings->username); /* If key specified, import */ - if (client_data->key_base64[0] != 0) { + if (settings->key_base64 != NULL) { guac_client_log(client, GUAC_LOG_DEBUG, "Attempting private key import (WITHOUT passphrase)"); /* Attempt to read key without passphrase */ if (guac_common_ssh_user_import_key(user, - client_data->key_base64, NULL)) { + settings->key_base64, NULL)) { /* Log failure of initial attempt */ guac_client_log(client, GUAC_LOG_DEBUG, @@ -101,15 +101,15 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { "Re-attempting private key import (WITH passphrase)"); /* Prompt for passphrase if missing */ - if (client_data->key_passphrase[0] == 0) - guac_terminal_prompt(client_data->term, "Key passphrase: ", - client_data->key_passphrase, - sizeof(client_data->key_passphrase), false); + if (settings->key_passphrase == NULL) + settings->key_passphrase = + guac_terminal_prompt(ssh_client->term, + "Key passphrase: ", false); /* Reattempt import with passphrase */ if (guac_common_ssh_user_import_key(user, - client_data->key_base64, - client_data->key_passphrase)) { + settings->key_base64, + settings->key_passphrase)) { /* If still failing, give up */ guac_client_abort(client, @@ -134,18 +134,17 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { else { /* Get password if not provided */ - if (client_data->password[0] == 0) - guac_terminal_prompt(client_data->term, "Password: ", - client_data->password, sizeof(client_data->password), - false); + if (settings->password == NULL) + settings->password = guac_terminal_prompt(ssh_client->term, + "Password: ", false); /* Set provided password */ - guac_common_ssh_user_set_password(user, client_data->password); + guac_common_ssh_user_set_password(user, settings->password); } /* Clear screen of any prompts */ - guac_terminal_printf(client_data->term, "\x1B[H\x1B[J"); + guac_terminal_printf(ssh_client->term, "\x1B[H\x1B[J"); return user; @@ -154,16 +153,16 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { void* ssh_input_thread(void* data) { guac_client* client = (guac_client*) data; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; char buffer[8192]; int bytes_read; /* Write all data read */ - while ((bytes_read = guac_terminal_read_stdin(client_data->term, buffer, sizeof(buffer))) > 0) { - pthread_mutex_lock(&(client_data->term_channel_lock)); - libssh2_channel_write(client_data->term_channel, buffer, bytes_read); - pthread_mutex_unlock(&(client_data->term_channel_lock)); + while ((bytes_read = guac_terminal_read_stdin(ssh_client->term, buffer, sizeof(buffer))) > 0) { + pthread_mutex_lock(&(ssh_client->term_channel_lock)); + libssh2_channel_write(ssh_client->term_channel, buffer, bytes_read); + pthread_mutex_unlock(&(ssh_client->term_channel_lock)); } return NULL; @@ -173,9 +172,9 @@ void* ssh_input_thread(void* data) { void* ssh_client_thread(void* data) { guac_client* client = (guac_client*) data; - ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_ssh_settings* settings = ssh_client->settings; - guac_socket* socket = client->socket; char buffer[8192]; pthread_t input_thread; @@ -184,29 +183,44 @@ void* ssh_client_thread(void* data) { if (guac_common_ssh_init(client)) return NULL; - /* Get user and credentials */ - client_data->user = guac_ssh_get_user(client); + /* Create terminal */ + ssh_client->term = guac_terminal_create(client, + settings->font_name, settings->font_size, + settings->resolution, settings->width, settings->height, + settings->color_scheme); - /* Send new name */ - char name[1024]; - snprintf(name, sizeof(name)-1, "%s@%s", - client_data->username, client_data->hostname); - guac_protocol_send_name(socket, name); + /* Fail if terminal init failed */ + if (ssh_client->term == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Terminal initialization failed"); + return NULL; + } + + /* Set up typescript, if requested */ + if (settings->typescript_path != NULL) { + guac_terminal_create_typescript(ssh_client->term, + settings->typescript_path, + settings->typescript_name, + settings->create_typescript_path); + } + + /* Get user and credentials */ + ssh_client->user = guac_ssh_get_user(client); /* Open SSH session */ - client_data->session = guac_common_ssh_create_session(client, - client_data->hostname, client_data->port, client_data->user); - if (client_data->session == NULL) { + ssh_client->session = guac_common_ssh_create_session(client, + settings->hostname, settings->port, ssh_client->user); + if (ssh_client->session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } - pthread_mutex_init(&client_data->term_channel_lock, NULL); + pthread_mutex_init(&ssh_client->term_channel_lock, NULL); /* Open channel for terminal */ - client_data->term_channel = - libssh2_channel_open_session(client_data->session->session); - if (client_data->term_channel == NULL) { + ssh_client->term_channel = + libssh2_channel_open_session(ssh_client->session->session); + if (ssh_client->term_channel == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to open terminal channel."); return NULL; @@ -214,59 +228,60 @@ void* ssh_client_thread(void* data) { #ifdef ENABLE_SSH_AGENT /* Start SSH agent forwarding, if enabled */ - if (client_data->enable_agent) { - libssh2_session_callback_set(client_data->session, + if (ssh_client->enable_agent) { + libssh2_session_callback_set(ssh_client->session, LIBSSH2_CALLBACK_AUTH_AGENT, (void*) ssh_auth_agent_callback); /* Request agent forwarding */ - if (libssh2_channel_request_auth_agent(client_data->term_channel)) + if (libssh2_channel_request_auth_agent(ssh_client->term_channel)) guac_client_log(client, GUAC_LOG_ERROR, "Agent forwarding request failed"); else guac_client_log(client, GUAC_LOG_INFO, "Agent forwarding enabled."); } - client_data->auth_agent = NULL; + ssh_client->auth_agent = NULL; #endif /* Start SFTP session as well, if enabled */ - if (client_data->enable_sftp) { + if (settings->enable_sftp) { /* Create SSH session specific for SFTP */ guac_client_log(client, GUAC_LOG_DEBUG, "Reconnecting for SFTP..."); - client_data->sftp_session = - guac_common_ssh_create_session(client, client_data->hostname, - client_data->port, client_data->user); - if (client_data->sftp_session == NULL) { + ssh_client->sftp_session = + guac_common_ssh_create_session(client, settings->hostname, + settings->port, ssh_client->user); + if (ssh_client->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; } /* Request SFTP */ - client_data->sftp_filesystem = - guac_common_ssh_create_sftp_filesystem( - client_data->sftp_session, "/"); + ssh_client->sftp_filesystem = guac_common_ssh_create_sftp_filesystem( + ssh_client->sftp_session, "/"); - /* Set generic (non-filesystem) file upload handler */ - client->file_handler = guac_sftp_file_handler; + /* Expose filesystem to connection owner */ + guac_client_for_owner(client, + guac_common_ssh_expose_sftp_filesystem, + ssh_client->sftp_filesystem); /* Init handlers for Guacamole-specific console codes */ - client_data->term->upload_path_handler = guac_sftp_set_upload_path; - client_data->term->file_download_handler = guac_sftp_download_file; + ssh_client->term->upload_path_handler = guac_sftp_set_upload_path; + ssh_client->term->file_download_handler = guac_sftp_download_file; guac_client_log(client, GUAC_LOG_DEBUG, "SFTP session initialized"); } /* Request PTY */ - if (libssh2_channel_request_pty_ex(client_data->term_channel, "linux", sizeof("linux")-1, NULL, 0, - client_data->term->term_width, client_data->term->term_height, 0, 0)) { + if (libssh2_channel_request_pty_ex(ssh_client->term_channel, "linux", sizeof("linux")-1, NULL, 0, + ssh_client->term->term_width, ssh_client->term->term_height, 0, 0)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to allocate PTY."); return NULL; } /* If a command is specified, run that instead of a shell */ - if (client_data->command != NULL) { - if (libssh2_channel_exec(client_data->term_channel, client_data->command)) { + if (settings->command != NULL) { + if (libssh2_channel_exec(ssh_client->term_channel, settings->command)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to execute command."); return NULL; @@ -274,7 +289,7 @@ void* ssh_client_thread(void* data) { } /* Otherwise, request a shell */ - else if (libssh2_channel_shell(client_data->term_channel)) { + else if (libssh2_channel_shell(ssh_client->term_channel)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, "Unable to associate shell with PTY."); return NULL; @@ -290,7 +305,7 @@ void* ssh_client_thread(void* data) { } /* Set non-blocking */ - libssh2_session_set_blocking(client_data->session->session, 0); + libssh2_session_set_blocking(ssh_client->session->session, 0); /* While data available, write to terminal */ int bytes_read = 0; @@ -299,23 +314,23 @@ void* ssh_client_thread(void* data) { /* Track total amount of data read */ int total_read = 0; - pthread_mutex_lock(&(client_data->term_channel_lock)); + pthread_mutex_lock(&(ssh_client->term_channel_lock)); /* Stop reading at EOF */ - if (libssh2_channel_eof(client_data->term_channel)) { - pthread_mutex_unlock(&(client_data->term_channel_lock)); + if (libssh2_channel_eof(ssh_client->term_channel)) { + pthread_mutex_unlock(&(ssh_client->term_channel_lock)); break; } /* Read terminal data */ - bytes_read = libssh2_channel_read(client_data->term_channel, + bytes_read = libssh2_channel_read(ssh_client->term_channel, buffer, sizeof(buffer)); - pthread_mutex_unlock(&(client_data->term_channel_lock)); + pthread_mutex_unlock(&(ssh_client->term_channel_lock)); /* Attempt to write data received. Exit on failure. */ if (bytes_read > 0) { - int written = guac_terminal_write_stdout(client_data->term, buffer, bytes_read); + int written = guac_terminal_write_stdout(ssh_client->term, buffer, bytes_read); if (written < 0) break; @@ -327,12 +342,12 @@ void* ssh_client_thread(void* data) { #ifdef ENABLE_SSH_AGENT /* If agent open, handle any agent packets */ - if (client_data->auth_agent != NULL) { - bytes_read = ssh_auth_agent_read(client_data->auth_agent); + if (ssh_client->auth_agent != NULL) { + bytes_read = ssh_auth_agent_read(ssh_client->auth_agent); if (bytes_read > 0) total_read += bytes_read; else if (bytes_read < 0 && bytes_read != LIBSSH2_ERROR_EAGAIN) - client_data->auth_agent = NULL; + ssh_client->auth_agent = NULL; } #endif @@ -342,13 +357,13 @@ void* ssh_client_thread(void* data) { struct timeval timeout; FD_ZERO(&fds); - FD_SET(client_data->session->fd, &fds); + FD_SET(ssh_client->session->fd, &fds); /* Wait for one second */ timeout.tv_sec = 1; timeout.tv_usec = 0; - if (select(client_data->session->fd + 1, &fds, + if (select(ssh_client->session->fd + 1, &fds, NULL, NULL, &timeout) < 0) break; } @@ -359,7 +374,7 @@ void* ssh_client_thread(void* data) { guac_client_stop(client); pthread_join(input_thread, NULL); - pthread_mutex_destroy(&client_data->term_channel_lock); + pthread_mutex_destroy(&ssh_client->term_channel_lock); guac_client_log(client, GUAC_LOG_INFO, "SSH connection ended."); return NULL; diff --git a/src/protocols/ssh/ssh.h b/src/protocols/ssh/ssh.h new file mode 100644 index 00000000..7611d0f2 --- /dev/null +++ b/src/protocols/ssh/ssh.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef GUAC_SSH_H +#define GUAC_SSH_H + +#include "config.h" + +#include "guac_sftp.h" +#include "guac_ssh.h" +#include "guac_ssh_user.h" +#include "settings.h" +#include "terminal.h" + +#ifdef ENABLE_SSH_AGENT +#include "ssh_agent.h" +#endif + +#include + +#include + +/** + * SSH-specific client data. + */ +typedef struct guac_ssh_client { + + /** + * SSH connection settings. + */ + guac_ssh_settings* settings; + +#ifdef ENABLE_SSH_AGENT + /** + * The current agent, if any. + */ + ssh_auth_agent* auth_agent; +#endif + + /** + * The SSH client thread. + */ + pthread_t client_thread; + + /** + * The user and credentials to use for all SSH sessions. + */ + guac_common_ssh_user* user; + + /** + * SSH session, used by the SSH client thread. + */ + guac_common_ssh_session* session; + + /** + * SFTP session, used by the SFTP client/filesystem. + */ + guac_common_ssh_session* sftp_session; + + /** + * The filesystem object exposed for the SFTP session. + */ + guac_common_ssh_sftp_filesystem* sftp_filesystem; + + /** + * SSH terminal channel, used by the SSH client thread. + */ + LIBSSH2_CHANNEL* term_channel; + + /** + * Lock dictating access to the SSH terminal channel. + */ + pthread_mutex_t term_channel_lock; + + /** + * The terminal which will render all output from the SSH client. + */ + guac_terminal* term; + +} guac_ssh_client ; + +/** + * Main SSH client thread, handling transfer of SSH output to STDOUT. + */ +void* ssh_client_thread(void* data); + +#endif + diff --git a/src/protocols/ssh/user.c b/src/protocols/ssh/user.c new file mode 100644 index 00000000..6b04857f --- /dev/null +++ b/src/protocols/ssh/user.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" + +#include "clipboard.h" +#include "input.h" +#include "guac_display.h" +#include "user.h" +#include "sftp.h" +#include "ssh.h" +#include "settings.h" + +#include +#include +#include + +#include +#include + +int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_client* client = user->client; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + + /* Connect via SSH if owner */ + if (user->owner) { + + /* Parse arguments into client */ + guac_ssh_settings* settings = ssh_client->settings = + guac_ssh_parse_args(user, argc, (const char**) argv); + + /* Fail if settings cannot be parsed */ + if (settings == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Badly formatted client arguments."); + return 1; + } + + /* Start client thread */ + if (pthread_create(&(ssh_client->client_thread), NULL, + ssh_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to start SSH client thread"); + return 1; + } + + } + + /* If not owner, synchronize with current display */ + else { + guac_terminal_dup(ssh_client->term, user, user->socket); + guac_socket_flush(user->socket); + } + + /* Set per-user event handlers */ + user->key_handler = guac_ssh_user_key_handler; + user->mouse_handler = guac_ssh_user_mouse_handler; + user->size_handler = guac_ssh_user_size_handler; + user->clipboard_handler = guac_ssh_clipboard_handler; + + /* Set generic (non-filesystem) file upload handler */ + user->file_handler = guac_sftp_file_handler; + + return 0; + +} + diff --git a/src/protocols/ssh/ssh_client.h b/src/protocols/ssh/user.h similarity index 83% rename from src/protocols/ssh/ssh_client.h rename to src/protocols/ssh/user.h index 2c8859cc..b459cbcd 100644 --- a/src/protocols/ssh/ssh_client.h +++ b/src/protocols/ssh/user.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2014 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,18 +20,17 @@ * THE SOFTWARE. */ - -#ifndef __SSH_CLIENT_H -#define __SSH_CLIENT_H +#ifndef GUAC_VNC_USER_H +#define GUAC_VNC_USER_H #include "config.h" -#include +#include /** - * Main SSH client thread, handling transfer of SSH output to STDOUT. + * Handler for joining users. */ -void* ssh_client_thread(void* data); +int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv); #endif From 7c2766b34bd10f0ae2dcd0b378696fb454498a76 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 9 Mar 2016 15:44:53 -0800 Subject: [PATCH 2/4] GUAC-1389: Abort correctly if password or keyboard-interactive auth fails (fixes GUAC-1381). --- src/common-ssh/guac_ssh.c | 47 +++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/common-ssh/guac_ssh.c b/src/common-ssh/guac_ssh.c index ca72703f..12156dee 100644 --- a/src/common-ssh/guac_ssh.c +++ b/src/common-ssh/guac_ssh.c @@ -344,19 +344,48 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) /* Authenticate with password, if provided */ else if (password != NULL) { - /* Authenticate with password */ + /* Check if password auth is supported on the server */ if (strstr(user_authlist, "password") != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, - "Using password authentication method"); - return libssh2_userauth_password(session, username, password); + + /* Attempt password authentication */ + if (libssh2_userauth_password(session, username, password)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Password authentication failed: %s", error_message); + + return 1; + } + + /* Password authentication succeeded */ + return 0; + } - /* Authenticate with password via keyboard-interactive auth */ + /* Check if keyboard-interactive auth is supported on the server */ if (strstr(user_authlist, "keyboard-interactive") != NULL) { - guac_client_log(client, GUAC_LOG_DEBUG, - "Using keyboard-interactive authentication method"); - return libssh2_userauth_keyboard_interactive(session, username, - &guac_common_ssh_kbd_callback); + + /* Attempt keyboard-interactive auth using provided password */ + if (libssh2_userauth_keyboard_interactive(session, username, + &guac_common_ssh_kbd_callback)) { + + /* Abort on failure */ + char* error_message; + libssh2_session_last_error(session, &error_message, NULL, 0); + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Keyboard-interactive authentication failed: %s", + error_message); + + return 1; + } + + /* Keyboard-interactive authentication succeeded */ + return 0; + } /* No known authentication types available */ From fa43a94e029535992bf118c7fdfb5d3f36ef7d41 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 14 Mar 2016 18:27:52 -0700 Subject: [PATCH 3/4] GUAC-1389: Rely on API definition for handler. --- src/protocols/ssh/client.h | 9 +-------- src/protocols/ssh/clipboard.h | 12 +++++------- src/protocols/ssh/input.h | 6 +++--- src/protocols/ssh/sftp.h | 21 +-------------------- src/protocols/ssh/user.h | 2 +- 5 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/protocols/ssh/client.h b/src/protocols/ssh/client.h index a9c1c17a..01290532 100644 --- a/src/protocols/ssh/client.h +++ b/src/protocols/ssh/client.h @@ -29,15 +29,8 @@ * Handler which is invoked when the SSH client needs to be disconnected (if * connected) and freed. This can happen if initialization fails, or all users * have left the connection. - * - * @param client - * The client associated with the SSH client to be freed. - * - * @return - * Zero on success, non-zero if an error occurs preventing the client from - * being entirely freed. */ -int guac_ssh_client_free_handler(guac_client* client); +guac_client_free_handler guac_ssh_client_free_handler; #endif diff --git a/src/protocols/ssh/clipboard.h b/src/protocols/ssh/clipboard.h index 0d6a4d84..59df4ca6 100644 --- a/src/protocols/ssh/clipboard.h +++ b/src/protocols/ssh/clipboard.h @@ -29,21 +29,19 @@ #include /** - * Handler for inbound clipboard data. + * Handler for inbound clipboard streams. */ -int guac_ssh_clipboard_handler(guac_user* user, guac_stream* stream, - char* mimetype); +guac_user_clipboard_handler guac_ssh_clipboard_handler; /** - * Handler for stream data related to clipboard. + * Handler for data received along clipboard streams. */ -int guac_ssh_clipboard_blob_handler(guac_user* user, guac_stream* stream, - void* data, int length); +guac_user_blob_handler guac_ssh_clipboard_blob_handler; /** * Handler for end-of-stream related to clipboard. */ -int guac_ssh_clipboard_end_handler(guac_user* user, guac_stream* stream); +guac_user_end_handler guac_ssh_clipboard_end_handler; #endif diff --git a/src/protocols/ssh/input.h b/src/protocols/ssh/input.h index 98607360..8de59c31 100644 --- a/src/protocols/ssh/input.h +++ b/src/protocols/ssh/input.h @@ -30,17 +30,17 @@ /** * Handler for Guacamole user mouse events. */ -int guac_ssh_user_mouse_handler(guac_user* user, int x, int y, int mask); +guac_user_mouse_handler guac_ssh_user_mouse_handler; /** * Handler for Guacamole user key events. */ -int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed); +guac_user_key_handler guac_ssh_user_key_handler; /** * Handler for Guacamole user size events. */ -int guac_ssh_user_size_handler(guac_user* user, int width, int height); +guac_user_size_handler guac_ssh_user_size_handler; #endif diff --git a/src/protocols/ssh/sftp.h b/src/protocols/ssh/sftp.h index 2beafdf8..3a2ec44f 100644 --- a/src/protocols/ssh/sftp.h +++ b/src/protocols/ssh/sftp.h @@ -34,27 +34,8 @@ * Handles an incoming stream from a Guacamole "file" instruction, saving the * contents of that stream to the file having the given name within the * upload directory set by guac_sftp_set_upload_path(). - * - * @param user - * The user receiving the uploaded file. - * - * @param stream - * The stream through which the uploaded file data will be received. - * - * @param mimetype - * The mimetype of the data being received. - * - * @param filename - * The filename of the file to write to. This filename will always be taken - * relative to the upload path set by - * guac_common_ssh_sftp_set_upload_path(). - * - * @return - * Zero if the incoming stream has been handled successfully, non-zero on - * failure. */ -int guac_sftp_file_handler(guac_user* user, guac_stream* stream, - char* mimetype, char* filename); +guac_user_file_handler guac_sftp_file_handler; /** * Initiates an SFTP file download to the user via the Guacamole "file" diff --git a/src/protocols/ssh/user.h b/src/protocols/ssh/user.h index b459cbcd..8bea5ee1 100644 --- a/src/protocols/ssh/user.h +++ b/src/protocols/ssh/user.h @@ -30,7 +30,7 @@ /** * Handler for joining users. */ -int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv); +guac_user_join_handler guac_ssh_user_join_handler; #endif From 3a4aec3708c2040d105e5c4808c983d07ce80988 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 14 Mar 2016 18:32:03 -0700 Subject: [PATCH 4/4] GUAC-1389: Document parameters of SSH client thread. --- src/protocols/ssh/ssh.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/protocols/ssh/ssh.h b/src/protocols/ssh/ssh.h index 7611d0f2..a773fe44 100644 --- a/src/protocols/ssh/ssh.h +++ b/src/protocols/ssh/ssh.h @@ -100,6 +100,14 @@ typedef struct guac_ssh_client { /** * Main SSH client thread, handling transfer of SSH output to STDOUT. + * + * @param data + * The guac_client to associate with a new SSH session, once the SSH + * connection succeeds. + * + * @return + * NULL in all cases. The return value of this thread is expected to be + * ignored. */ void* ssh_client_thread(void* data);