diff --git a/Makefile.am b/Makefile.am index ec52ed37..9b5ab08f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,7 +58,7 @@ SUBDIRS += src/protocols/ssh endif if ENABLE_TELNET -#SUBDIRS += src/protocols/telnet +SUBDIRS += src/protocols/telnet endif if ENABLE_VNC diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index fdaf00f0..2073229c 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -28,14 +28,18 @@ lib_LTLIBRARIES = libguac-client-telnet.la libguac_client_telnet_la_SOURCES = \ client.c \ clipboard.c \ - guac_handlers.c \ - telnet_client.c + input.c \ + settings.c \ + telnet.c \ + user.c -noinst_HEADERS = \ - client.h \ - clipboard.h \ - guac_handlers.h \ - telnet_client.h +noinst_HEADERS = \ + client.h \ + clipboard.h \ + input.h \ + settings.h \ + telnet.h \ + user.h libguac_client_telnet_la_CFLAGS = \ -Werror -Wall -Iinclude \ diff --git a/src/protocols/telnet/client.c b/src/protocols/telnet/client.c index e4f18cd6..0c61f70c 100644 --- a/src/protocols/telnet/client.c +++ b/src/protocols/telnet/client.c @@ -22,260 +22,43 @@ #include "config.h" #include "client.h" -#include "clipboard.h" -#include "guac_handlers.h" -#include "telnet_client.h" +#include "settings.h" +#include "telnet.h" #include "terminal.h" +#include "user.h" #include #include #include -#include #include #include -#include #include -#include -#include -#define GUAC_TELNET_DEFAULT_FONT_NAME "monospace" -#define GUAC_TELNET_DEFAULT_FONT_SIZE 12 -#define GUAC_TELNET_DEFAULT_PORT "23" +int guac_client_init(guac_client* client) { -/* Client plugin arguments */ -const char* GUAC_CLIENT_ARGS[] = { - "hostname", - "port", - "username", - "username-regex", - "password", - "password-regex", - "font-name", - "font-size", - "color-scheme", - "typescript-path", - "typescript-name", - "create-typescript-path", - NULL -}; + /* Set client args */ + client->args = GUAC_TELNET_CLIENT_ARGS; -enum __TELNET_ARGS_IDX { + /* Allocate client instance data */ + guac_telnet_client* telnet_client = calloc(1, sizeof(guac_telnet_client)); + client->data = telnet_client; - /** - * The hostname to connect to. Required. - */ - IDX_HOSTNAME, + /* Init telnet client */ + telnet_client->socket_fd = -1; + telnet_client->naws_enabled = 0; + telnet_client->echo_enabled = 1; - /** - * The port to connect to. Optional. - */ - IDX_PORT, - - /** - * The name of the user to login as. Optional. - */ - IDX_USERNAME, - - /** - * The regular expression to use when searching for the username/login prompt. - * Optional. - */ - IDX_USERNAME_REGEX, - - /** - * The password to use when logging in. Optional. - */ - IDX_PASSWORD, - - /** - * The regular expression to use when searching for the password prompt. - * Optional. - */ - IDX_PASSWORD_REGEX, - - /** - * 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, - - /** - * 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 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, - - TELNET_ARGS_COUNT -}; - -/** - * Compiles the given regular expression, returning NULL if compilation fails. - */ -static regex_t* __guac_telnet_compile_regex(guac_client* client, char* pattern) { - - int compile_result; - regex_t* regex = malloc(sizeof(regex_t)); - - /* Compile regular expression */ - compile_result = regcomp(regex, pattern,REG_EXTENDED | REG_NOSUB | REG_ICASE | REG_NEWLINE); - - /* Notify of failure to parse/compile */ - if (compile_result != 0) { - guac_client_log(client, GUAC_LOG_ERROR, "Regular expression '%s' could not be compiled.", pattern); - free(regex); - return NULL; - } - - return regex; -} - -int guac_client_init(guac_client* client, int argc, char** argv) { - - guac_socket* socket = client->socket; - - guac_telnet_client_data* client_data = malloc(sizeof(guac_telnet_client_data)); - - /* Init client data */ - client->data = client_data; - client_data->telnet = NULL; - client_data->socket_fd = -1; - client_data->naws_enabled = 0; - client_data->echo_enabled = 1; - - if (argc != TELNET_ARGS_COUNT) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Wrong number of arguments"); - return -1; - } + /* Set handlers */ + client->join_handler = guac_telnet_user_join_handler; + client->free_handler = guac_telnet_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]); - - /* Set username regex, if needed */ - if (client_data->username[0] != 0) { - - /* Compile regular expression */ - if (argv[IDX_USERNAME_REGEX][0] != 0) - client_data->username_regex = __guac_telnet_compile_regex(client, argv[IDX_USERNAME_REGEX]); - else - client_data->username_regex = __guac_telnet_compile_regex(client, GUAC_TELNET_DEFAULT_USERNAME_REGEX); - - } - else - client_data->username_regex = NULL; - - /* Set password regex, if needed */ - if (client_data->password[0] != 0) { - - /* Compile regular expression */ - if (argv[IDX_PASSWORD_REGEX][0] != 0) - client_data->password_regex = __guac_telnet_compile_regex(client, argv[IDX_PASSWORD_REGEX]); - else - client_data->password_regex = __guac_telnet_compile_regex(client, GUAC_TELNET_DEFAULT_PASSWORD_REGEX); - - } - else - client_data->password_regex = NULL; - - /* Read port */ - if (argv[IDX_PORT][0] != 0) - strcpy(client_data->port, argv[IDX_PORT]); - else - strcpy(client_data->port, GUAC_TELNET_DEFAULT_PORT); - - /* 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_TELNET_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_TELNET_DEFAULT_FONT_SIZE; - - /* 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); - - } - - /* Send initial name */ - guac_protocol_send_name(socket, client_data->hostname); - - guac_socket_flush(socket); - - /* Set basic handlers */ - client->handle_messages = guac_telnet_client_handle_messages; - client->key_handler = guac_telnet_client_key_handler; - client->mouse_handler = guac_telnet_client_mouse_handler; - client->size_handler = guac_telnet_client_size_handler; - client->free_handler = guac_telnet_client_free_handler; - client->clipboard_handler = guac_telnet_clipboard_handler; - - /* Start client thread */ - if (pthread_create(&(client_data->client_thread), NULL, guac_telnet_client_thread, (void*) client)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start telnet 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 */ @@ -283,3 +66,29 @@ int guac_client_init(guac_client* client, int argc, char** argv) { } +int guac_telnet_client_free_handler(guac_client* client) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Close telnet connection */ + if (telnet_client->socket_fd != -1) + close(telnet_client->socket_fd); + + /* Kill terminal */ + guac_terminal_free(telnet_client->term); + + /* Wait for and free telnet session, if connected */ + if (telnet_client->telnet != NULL) { + pthread_join(telnet_client->client_thread, NULL); + telnet_free(telnet_client->telnet); + } + + /* Free settings */ + if (telnet_client->settings != NULL) + guac_telnet_settings_free(telnet_client->settings); + + free(telnet_client); + return 0; + +} + diff --git a/src/protocols/telnet/client.h b/src/protocols/telnet/client.h index e72a1a63..32246fb2 100644 --- a/src/protocols/telnet/client.h +++ b/src/protocols/telnet/client.h @@ -32,91 +32,11 @@ #include -#define GUAC_TELNET_DEFAULT_USERNAME_REGEX "[Ll]ogin:" -#define GUAC_TELNET_DEFAULT_PASSWORD_REGEX "[Pp]assword:" - /** - * Telnet-specific client data. + * Free handler. Required by libguac and called when the guac_client is + * disconnected and must be cleaned up. */ -typedef struct guac_telnet_client_data { - - /** - * The hostname of the telnet server to connect to. - */ - char hostname[1024]; - - /** - * The port of the telnet server to connect to. - */ - char port[64]; - - /** - * The name of the user to login as. - */ - char username[1024]; - - /** - * The regular expression to use when searching for the username - * prompt. This will be NULL unless the telnet client is currently - * searching for the username prompt. - */ - regex_t* username_regex; - - /** - * The password to give when authenticating. - */ - char password[1024]; - - /** - * The regular expression to use when searching for the password - * prompt. This will be NULL unless the telnet client is currently - * searching for the password prompt. - */ - regex_t* password_regex; - - /** - * 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; - - /** - * The telnet client thread. - */ - pthread_t client_thread; - - /** - * The file descriptor of the socket connected to the telnet server, - * or -1 if no connection has been established. - */ - int socket_fd; - - /** - * Telnet connection, used by the telnet client thread. - */ - telnet_t* telnet; - - /** - * Whether window size should be sent when the window is resized. - */ - int naws_enabled; - - /** - * Whether all user input should be automatically echoed to the - * terminal. - */ - int echo_enabled; - - /** - * The terminal which will render all output from the telnet client. - */ - guac_terminal* term; - -} guac_telnet_client_data; +guac_client_free_handler guac_telnet_client_free_handler; #endif diff --git a/src/protocols/telnet/clipboard.c b/src/protocols/telnet/clipboard.c index f8ff54ce..2f02afd0 100644 --- a/src/protocols/telnet/clipboard.c +++ b/src/protocols/telnet/clipboard.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * Copyright (C) 2016 Glyptodon, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,19 +21,21 @@ */ #include "config.h" -#include "client.h" #include "clipboard.h" +#include "telnet.h" #include "terminal.h" #include #include +#include -int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, +int guac_telnet_clipboard_handler(guac_user* user, guac_stream* stream, char* mimetype) { /* Clear clipboard and prepare for new data */ - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; - guac_terminal_clipboard_reset(client_data->term, mimetype); + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_terminal_clipboard_reset(telnet_client->term, mimetype); /* Set handlers for clipboard stream */ stream->blob_handler = guac_telnet_clipboard_blob_handler; @@ -42,17 +44,18 @@ int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, return 0; } -int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, +int guac_telnet_clipboard_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { /* Append new data */ - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; - guac_terminal_clipboard_append(client_data->term, data, length); + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_terminal_clipboard_append(telnet_client->term, data, length); return 0; } -int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream) { +int guac_telnet_clipboard_end_handler(guac_user* user, guac_stream* stream) { /* Nothing to do - clipboard is implemented within client */ diff --git a/src/protocols/telnet/clipboard.h b/src/protocols/telnet/clipboard.h index 8823bb4f..c6756d78 100644 --- a/src/protocols/telnet/clipboard.h +++ b/src/protocols/telnet/clipboard.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * Copyright (C) 2016 Glyptodon, Inc. * * 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,30 +20,27 @@ * THE SOFTWARE. */ -#ifndef GUAC_TELNET__CLIPBOARD_H -#define GUAC_TELNET__CLIPBOARD_H +#ifndef GUAC_TELNET_CLIPBOARD_H +#define GUAC_TELNET_CLIPBOARD_H #include "config.h" -#include -#include +#include /** - * Handler for inbound clipboard data. + * Handler for inbound clipboard streams. */ -int guac_telnet_clipboard_handler(guac_client* client, guac_stream* stream, - char* mimetype); +guac_user_clipboard_handler guac_telnet_clipboard_handler; /** - * Handler for stream data related to clipboard. + * Handler for data received along clipboard streams. */ -int guac_telnet_clipboard_blob_handler(guac_client* client, guac_stream* stream, - void* data, int length); +guac_user_blob_handler guac_telnet_clipboard_blob_handler; /** * Handler for end-of-stream related to clipboard. */ -int guac_telnet_clipboard_end_handler(guac_client* client, guac_stream* stream); +guac_user_end_handler guac_telnet_clipboard_end_handler; #endif diff --git a/src/protocols/telnet/guac_handlers.c b/src/protocols/telnet/guac_handlers.c deleted file mode 100644 index 9832eb52..00000000 --- a/src/protocols/telnet/guac_handlers.c +++ /dev/null @@ -1,150 +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 "terminal.h" -#include "telnet_client.h" - -#include -#include - -#include -#include -#include -#include -#include - -int guac_telnet_client_handle_messages(guac_client* client) { - - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; - return guac_terminal_render_frame(client_data->term); - -} - -int guac_telnet_client_mouse_handler(guac_client* client, int x, int y, int mask) { - - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; - guac_terminal* term = client_data->term; - - /* Send mouse if not searching for password or username */ - if (client_data->password_regex == NULL && client_data->username_regex == NULL) - guac_terminal_send_mouse(term, x, y, mask); - - return 0; - -} - -int guac_telnet_client_key_handler(guac_client* client, int keysym, int pressed) { - - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; - guac_terminal* term = client_data->term; - - /* Stop searching for password */ - if (client_data->password_regex != NULL) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Stopping password prompt search due to user input."); - - regfree(client_data->password_regex); - free(client_data->password_regex); - client_data->password_regex = NULL; - - } - - /* Stop searching for username */ - if (client_data->username_regex != NULL) { - - guac_client_log(client, GUAC_LOG_DEBUG, - "Stopping username prompt search due to user input."); - - regfree(client_data->username_regex); - free(client_data->username_regex); - client_data->username_regex = NULL; - - } - - /* Intercept and handle Pause / Break / Ctrl+0 as "IAC BRK" */ - if (pressed && ( - keysym == 0xFF13 /* Pause */ - || keysym == 0xFF6B /* Break */ - || (term->mod_ctrl && keysym == '0') /* Ctrl + 0 */ - )) { - - /* Send IAC BRK */ - telnet_iac(client_data->telnet, TELNET_BREAK); - - return 0; - } - - /* Send key */ - guac_terminal_send_key(term, keysym, pressed); - - return 0; - -} - -int guac_telnet_client_size_handler(guac_client* client, int width, int height) { - - /* Get terminal */ - guac_telnet_client_data* guac_client_data = (guac_telnet_client_data*) client->data; - guac_terminal* terminal = guac_client_data->term; - - /* Resize terminal */ - guac_terminal_resize(terminal, width, height); - - /* Update terminal window size if connected */ - if (guac_client_data->telnet != NULL && guac_client_data->naws_enabled) - guac_telnet_send_naws(guac_client_data->telnet, terminal->term_width, terminal->term_height); - - return 0; -} - -int guac_telnet_client_free_handler(guac_client* client) { - - guac_telnet_client_data* guac_client_data = (guac_telnet_client_data*) client->data; - - /* Close telnet connection */ - if (guac_client_data->socket_fd != -1) - close(guac_client_data->socket_fd); - - /* Kill terminal */ - guac_terminal_free(guac_client_data->term); - - /* Wait for and free telnet session, if connected */ - if (guac_client_data->telnet != NULL) { - pthread_join(guac_client_data->client_thread, NULL); - telnet_free(guac_client_data->telnet); - } - - /* Free password regex */ - if (guac_client_data->password_regex != NULL) { - regfree(guac_client_data->password_regex); - free(guac_client_data->password_regex); - } - - free(client->data); - return 0; - -} - diff --git a/src/protocols/telnet/input.c b/src/protocols/telnet/input.c new file mode 100644 index 00000000..f73e836a --- /dev/null +++ b/src/protocols/telnet/input.c @@ -0,0 +1,132 @@ +/* + * 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 "input.h" +#include "terminal.h" +#include "telnet.h" + +#include +#include +#include + +#include +#include +#include +#include + +int guac_telnet_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; + guac_terminal* term = telnet_client->term; + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Send mouse if not searching for password or username */ + if (settings->password_regex == NULL && settings->username_regex == NULL) + guac_terminal_send_mouse(term, user, x, y, mask); + + return 0; + +} + +int guac_telnet_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; + guac_terminal* term = telnet_client->term; + + /* Skip if terminal not yet ready */ + if (term == NULL) + return 0; + + /* Stop searching for password */ + if (settings->password_regex != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Stopping password prompt search due to user input."); + + regfree(settings->password_regex); + free(settings->password_regex); + settings->password_regex = NULL; + + } + + /* Stop searching for username */ + if (settings->username_regex != NULL) { + + guac_client_log(client, GUAC_LOG_DEBUG, + "Stopping username prompt search due to user input."); + + regfree(settings->username_regex); + free(settings->username_regex); + settings->username_regex = NULL; + + } + + /* Intercept and handle Pause / Break / Ctrl+0 as "IAC BRK" */ + if (pressed && ( + keysym == 0xFF13 /* Pause */ + || keysym == 0xFF6B /* Break */ + || (term->mod_ctrl && keysym == '0') /* Ctrl + 0 */ + )) { + + /* Send IAC BRK */ + telnet_iac(telnet_client->telnet, TELNET_BREAK); + + return 0; + } + + /* Send key */ + guac_terminal_send_key(term, keysym, pressed); + + return 0; + +} + +int guac_telnet_user_size_handler(guac_user* user, int width, int height) { + + /* Get terminal */ + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_terminal* terminal = telnet_client->term; + + /* Skip if terminal not yet ready */ + if (terminal == NULL) + return 0; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + /* Update terminal window size if connected */ + if (telnet_client->telnet != NULL && telnet_client->naws_enabled) + guac_telnet_send_naws(telnet_client->telnet, terminal->term_width, + terminal->term_height); + + return 0; +} + diff --git a/src/protocols/telnet/guac_handlers.h b/src/protocols/telnet/input.h similarity index 66% rename from src/protocols/telnet/guac_handlers.h rename to src/protocols/telnet/input.h index d2b67b7d..b4a6bca9 100644 --- a/src/protocols/telnet/guac_handlers.h +++ b/src/protocols/telnet/input.h @@ -20,43 +20,30 @@ * THE SOFTWARE. */ -#ifndef GUAC_TELNET__GUAC_HANDLERS_H -#define GUAC_TELNET__GUAC_HANDLERS_H +#ifndef GUAC_TELNET_INPUT_H +#define GUAC_TELNET_INPUT_H #include "config.h" -#include - -/** - * Generic handler for sending outbound messages. Required by libguac and - * called periodically by guacd when the client is ready for more graphical - * updates. - */ -int guac_telnet_client_handle_messages(guac_client* client); +#include /** * Handler for key events. Required by libguac and called whenever key events * are received. */ -int guac_telnet_client_key_handler(guac_client* client, int keysym, int pressed); +guac_user_key_handler guac_telnet_user_key_handler; /** * Handler for mouse events. Required by libguac and called whenever mouse * events are received. */ -int guac_telnet_client_mouse_handler(guac_client* client, int x, int y, int mask); +guac_user_mouse_handler guac_telnet_user_mouse_handler; /** * Handler for size events. Required by libguac and called whenever the remote * display (window) is resized. */ -int guac_telnet_client_size_handler(guac_client* client, int width, int height); - -/** - * Free handler. Required by libguac and called when the guac_client is - * disconnected and must be cleaned up. - */ -int guac_telnet_client_free_handler(guac_client* client); +guac_user_size_handler guac_telnet_user_size_handler; #endif diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c new file mode 100644 index 00000000..38fbc22e --- /dev/null +++ b/src/protocols/telnet/settings.c @@ -0,0 +1,281 @@ +/* + * 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 "settings.h" + +#include + +#include +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_TELNET_CLIENT_ARGS[] = { + "hostname", + "port", + "username", + "username-regex", + "password", + "password-regex", + "font-name", + "font-size", + "color-scheme", + "typescript-path", + "typescript-name", + "create-typescript-path", + NULL +}; + +enum TELNET_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 regular expression to use when searching for the username/login + * prompt. Optional. + */ + IDX_USERNAME_REGEX, + + /** + * The password to use when logging in. Optional. + */ + IDX_PASSWORD, + + /** + * The regular expression to use when searching for the password prompt. + * Optional. + */ + IDX_PASSWORD_REGEX, + + /** + * 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, + + /** + * 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 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, + + TELNET_ARGS_COUNT +}; + +/** + * Compiles the given regular expression, returning NULL if compilation fails. + * The returned regex_t must be freed with regfree() AND free(). + * + * @param user + * The user who provided the setting associated with the given regex + * pattern. Error messages will be logged on behalf of this user. + * + * @param pattern + * The regular expression pattern to compile. + * + * @return + * The compiled regular expression, or NULL if compilation fails. + */ +static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { + + int compile_result; + regex_t* regex = malloc(sizeof(regex_t)); + + /* Compile regular expression */ + compile_result = regcomp(regex, pattern, + REG_EXTENDED | REG_NOSUB | REG_ICASE | REG_NEWLINE); + + /* Notify of failure to parse/compile */ + if (compile_result != 0) { + guac_user_log(user, GUAC_LOG_ERROR, "Regular expression '%s' " + "could not be compiled.", pattern); + free(regex); + return NULL; + } + + return regex; +} + +guac_telnet_settings* guac_telnet_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != TELNET_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + TELNET_ARGS_COUNT, argc); + return NULL; + } + + guac_telnet_settings* settings = calloc(1, sizeof(guac_telnet_settings)); + + /* Read parameters */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + /* Read username */ + settings->username = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_USERNAME, NULL); + + /* Read username regex only if password is specified */ + if (settings->username != NULL) { + settings->username_regex = guac_telnet_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_USERNAME_REGEX, GUAC_TELNET_DEFAULT_USERNAME_REGEX)); + } + + /* Read password */ + settings->password = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_PASSWORD, NULL); + + /* Read password regex only if password is specified */ + if (settings->password != NULL) { + settings->password_regex = guac_telnet_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX)); + } + + /* Read font name */ + settings->font_name = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_FONT_NAME, GUAC_TELNET_DEFAULT_FONT_NAME); + + /* Read font size */ + settings->font_size = + guac_user_parse_args_int(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_FONT_SIZE, GUAC_TELNET_DEFAULT_FONT_SIZE); + + /* Copy requested color scheme */ + settings->color_scheme = + guac_user_parse_args_string(user, GUAC_TELNET_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; + + /* Read port */ + settings->port = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_PORT, GUAC_TELNET_DEFAULT_PORT); + + /* Read typescript path */ + settings->typescript_path = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_PATH, NULL); + + /* Read typescript name */ + settings->typescript_name = + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_NAME, GUAC_TELNET_DEFAULT_TYPESCRIPT_NAME); + + /* Parse path creation flag */ + settings->create_typescript_path = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_CREATE_TYPESCRIPT_PATH, false); + + /* Parsing was successful */ + return settings; + +} + +void guac_telnet_settings_free(guac_telnet_settings* settings) { + + /* Free network connection information */ + free(settings->hostname); + free(settings->port); + + /* Free credentials */ + free(settings->username); + free(settings->password); + + /* Free username regex (if allocated) */ + if (settings->username_regex != NULL) { + regfree(settings->username_regex); + free(settings->username_regex); + } + + /* Free password regex (if allocated) */ + if (settings->password_regex != NULL) { + regfree(settings->password_regex); + free(settings->password_regex); + } + + /* Free display preferences */ + free(settings->font_name); + free(settings->color_scheme); + + /* Free typescript settings */ + free(settings->typescript_name); + free(settings->typescript_path); + + /* Free overall structure */ + free(settings); + +} + diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h new file mode 100644 index 00000000..54062206 --- /dev/null +++ b/src/protocols/telnet/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_TELNET_SETTINGS_H +#define GUAC_TELNET_SETTINGS_H + +#include "config.h" + +#include + +#include +#include +#include + +/** + * The name of the font to use for the terminal if no name is specified. + */ +#define GUAC_TELNET_DEFAULT_FONT_NAME "monospace" + +/** + * The size of the font to use for the terminal if no font size is specified, + * in points. + */ +#define GUAC_TELNET_DEFAULT_FONT_SIZE 12 + +/** + * The port to connect to when initiating any telnet connection, if no other + * port is specified. + */ +#define GUAC_TELNET_DEFAULT_PORT "23" + +/** + * The filename to use for the typescript, if not specified. + */ +#define GUAC_TELNET_DEFAULT_TYPESCRIPT_NAME "typescript" + +/** + * The regular expression to use when searching for the username/login prompt + * if no other regular expression is specified. + */ +#define GUAC_TELNET_DEFAULT_USERNAME_REGEX "[Ll]ogin:" + +/** + * The regular expression to use when searching for the password prompt if no + * other regular expression is specified. + */ +#define GUAC_TELNET_DEFAULT_PASSWORD_REGEX "[Pp]assword:" + +/** + * Settings for the telnet connection. The values for this structure are parsed + * from the arguments given during the Guacamole protocol handshake using the + * guac_telnet_parse_args() function. + */ +typedef struct guac_telnet_settings { + + /** + * The hostname of the telnet server to connect to. + */ + char* hostname; + + /** + * The port of the telnet 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 regular expression to use when searching for the username/login + * prompt. If no username is specified, this will be NULL. If a username + * is specified, this will either be the specified username regex, or the + * default username regex. + */ + regex_t* username_regex; + + /** + * The password to give when authenticating, if any. If no password is + * specified, this will be NULL. + */ + char* password; + + /** + * The regular expression to use when searching for the password prompt. If + * no password is specified, this will be NULL. If a password is specified, + * this will either be the specified password regex, or the default + * password regex. + */ + regex_t* password_regex; + + /** + * 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; + + /** + * 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_telnet_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_telnet_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_telnet_settings* guac_telnet_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_telnet_settings object, having been previously + * allocated via guac_telnet_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_telnet_settings_free(guac_telnet_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_TELNET_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/telnet/telnet_client.c b/src/protocols/telnet/telnet.c similarity index 74% rename from src/protocols/telnet/telnet_client.c rename to src/protocols/telnet/telnet.c index 6adf145f..526000d2 100644 --- a/src/protocols/telnet/telnet_client.c +++ b/src/protocols/telnet/telnet.c @@ -21,8 +21,7 @@ */ #include "config.h" -#include "client.h" -#include "telnet_client.h" +#include "telnet.h" #include "terminal.h" #include @@ -95,7 +94,7 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char static char line_buffer[1024] = {0}; static int length = 0; - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; int i; const char* current; @@ -126,9 +125,9 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char if (regexec(regex, line_buffer, 0, NULL, 0) == 0) { /* Send value */ - guac_terminal_send_string(client_data->term, value); - guac_terminal_send_key(client_data->term, 0xFF0D, 1); - guac_terminal_send_key(client_data->term, 0xFF0D, 0); + guac_terminal_send_string(telnet_client->term, value); + guac_terminal_send_key(telnet_client->term, 0xFF0D, 1); + guac_terminal_send_key(telnet_client->term, 0xFF0D, 0); /* Stop searching for prompt */ return TRUE; @@ -146,49 +145,52 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, void* data) { guac_client* client = (guac_client*) data; - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; switch (event->type) { /* Terminal output received */ case TELNET_EV_DATA: - guac_terminal_write_stdout(client_data->term, event->data.buffer, event->data.size); + guac_terminal_write_stdout(telnet_client->term, event->data.buffer, event->data.size); /* Continue search for username prompt */ - if (client_data->username_regex != NULL) { - if (__guac_telnet_regex_search(client, client_data->username_regex, client_data->username, - event->data.buffer, event->data.size)) { + if (settings->username_regex != NULL) { + if (__guac_telnet_regex_search(client, + settings->username_regex, settings->username, + event->data.buffer, event->data.size)) { guac_client_log(client, GUAC_LOG_DEBUG, "Username sent"); - regfree(client_data->username_regex); - free(client_data->username_regex); - client_data->username_regex = NULL; + regfree(settings->username_regex); + free(settings->username_regex); + settings->username_regex = NULL; } } /* Continue search for password prompt */ - if (client_data->password_regex != NULL) { - if (__guac_telnet_regex_search(client, client_data->password_regex, client_data->password, - event->data.buffer, event->data.size)) { + if (settings->password_regex != NULL) { + if (__guac_telnet_regex_search(client, + settings->password_regex, settings->password, + event->data.buffer, event->data.size)) { guac_client_log(client, GUAC_LOG_DEBUG, "Password sent"); /* Do not continue searching for username once password is sent */ - if (client_data->username_regex != NULL) { - regfree(client_data->username_regex); - free(client_data->username_regex); - client_data->username_regex = NULL; + if (settings->username_regex != NULL) { + regfree(settings->username_regex); + free(settings->username_regex); + settings->username_regex = NULL; } - regfree(client_data->password_regex); - free(client_data->password_regex); - client_data->password_regex = NULL; + regfree(settings->password_regex); + free(settings->password_regex); + settings->password_regex = NULL; } } break; /* Data destined for remote end */ case TELNET_EV_SEND: - if (__guac_telnet_write_all(client_data->socket_fd, event->data.buffer, event->data.size) + if (__guac_telnet_write_all(telnet_client->socket_fd, event->data.buffer, event->data.size) != event->data.size) guac_client_stop(client); break; @@ -196,27 +198,27 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, /* Remote feature enabled */ case TELNET_EV_WILL: if (event->neg.telopt == TELNET_TELOPT_ECHO) - client_data->echo_enabled = 0; /* Disable local echo, as remote will echo */ + telnet_client->echo_enabled = 0; /* Disable local echo, as remote will echo */ break; /* Remote feature disabled */ case TELNET_EV_WONT: if (event->neg.telopt == TELNET_TELOPT_ECHO) - client_data->echo_enabled = 1; /* Enable local echo, as remote won't echo */ + telnet_client->echo_enabled = 1; /* Enable local echo, as remote won't echo */ break; /* Local feature enable */ case TELNET_EV_DO: if (event->neg.telopt == TELNET_TELOPT_NAWS) { - client_data->naws_enabled = 1; - guac_telnet_send_naws(telnet, client_data->term->term_width, client_data->term->term_height); + telnet_client->naws_enabled = 1; + guac_telnet_send_naws(telnet, telnet_client->term->term_width, telnet_client->term->term_height); } break; /* Terminal type request */ case TELNET_EV_TTYPE: if (event->ttype.cmd == TELNET_TTYPE_SEND) - telnet_ttype_is(client_data->telnet, "linux"); + telnet_ttype_is(telnet_client->telnet, "linux"); break; /* Environment request */ @@ -224,7 +226,7 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, /* Only send USER if entire environment was requested */ if (event->environ.size == 0) - guac_telnet_send_user(telnet, client_data->username); + guac_telnet_send_user(telnet, settings->username); break; @@ -258,16 +260,16 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, static void* __guac_telnet_input_thread(void* data) { guac_client* client = (guac_client*) data; - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; + guac_telnet_client* telnet_client = (guac_telnet_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) { - telnet_send(client_data->telnet, buffer, bytes_read); - if (client_data->echo_enabled) - guac_terminal_write_stdout(client_data->term, buffer, bytes_read); + while ((bytes_read = guac_terminal_read_stdin(telnet_client->term, buffer, sizeof(buffer))) > 0) { + telnet_send(telnet_client->telnet, buffer, bytes_read); + if (telnet_client->echo_enabled) + guac_terminal_write_stdout(telnet_client->term, buffer, bytes_read); } return NULL; @@ -293,7 +295,8 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { char connected_address[1024]; char connected_port[64]; - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; struct addrinfo hints = { .ai_family = AF_UNSPEC, @@ -305,7 +308,7 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { fd = socket(AF_INET, SOCK_STREAM, 0); /* Get addresses connection */ - if ((retval = getaddrinfo(client_data->hostname, client_data->port, + if ((retval = getaddrinfo(settings->hostname, settings->port, &hints, &addresses))) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error parsing given address or port: %s", gai_strerror(retval)); @@ -366,7 +369,7 @@ static telnet_t* __guac_telnet_create_session(guac_client* client) { } /* Save file descriptor */ - client_data->socket_fd = fd; + telnet_client->socket_fd = fd; return telnet; @@ -412,13 +415,18 @@ void guac_telnet_send_user(telnet_t* telnet, const char* username) { telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_IS); - /* VAR "USER" */ - __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VAR); - telnet_send(telnet, "USER", 4); + /* Only send username if defined */ + if (username != NULL) { - /* VALUE username */ - __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VALUE); - telnet_send(telnet, username, strlen(username)); + /* VAR "USER" */ + __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VAR); + telnet_send(telnet, "USER", 4); + + /* VALUE username */ + __guac_telnet_send_uint8(telnet, TELNET_ENVIRON_VALUE); + telnet_send(telnet, username, strlen(username)); + + } /* IAC SE */ telnet_finish_sb(telnet); @@ -453,15 +461,37 @@ static int __guac_telnet_wait(int socket_fd) { void* guac_telnet_client_thread(void* data) { guac_client* client = (guac_client*) data; - guac_telnet_client_data* client_data = (guac_telnet_client_data*) client->data; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; pthread_t input_thread; char buffer[8192]; int wait_result; + /* Create terminal */ + telnet_client->term = guac_terminal_create(client, + settings->font_name, settings->font_size, + settings->resolution, settings->width, settings->height, + settings->color_scheme); + + /* Fail if terminal init failed */ + if (telnet_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(telnet_client->term, + settings->typescript_path, + settings->typescript_name, + settings->create_typescript_path); + } + /* Open telnet session */ - client_data->telnet = __guac_telnet_create_session(client); - if (client_data->telnet == NULL) { + telnet_client->telnet = __guac_telnet_create_session(client); + if (telnet_client->telnet == NULL) { /* Already aborted within __guac_telnet_create_session() */ return NULL; } @@ -476,17 +506,17 @@ void* guac_telnet_client_thread(void* data) { } /* While data available, write to terminal */ - while ((wait_result = __guac_telnet_wait(client_data->socket_fd)) >= 0) { + while ((wait_result = __guac_telnet_wait(telnet_client->socket_fd)) >= 0) { /* Resume waiting of no data available */ if (wait_result == 0) continue; - int bytes_read = read(client_data->socket_fd, buffer, sizeof(buffer)); + int bytes_read = read(telnet_client->socket_fd, buffer, sizeof(buffer)); if (bytes_read <= 0) break; - telnet_recv(client_data->telnet, buffer, bytes_read); + telnet_recv(telnet_client->telnet, buffer, bytes_read); } diff --git a/src/protocols/telnet/telnet_client.h b/src/protocols/telnet/telnet.h similarity index 63% rename from src/protocols/telnet/telnet_client.h rename to src/protocols/telnet/telnet.h index 6c72bde5..56b0a2c0 100644 --- a/src/protocols/telnet/telnet_client.h +++ b/src/protocols/telnet/telnet.h @@ -20,15 +20,61 @@ * THE SOFTWARE. */ -#ifndef GUAC_TELNET__TELNET_CLIENT_H -#define GUAC_TELNET__TELNET_CLIENT_H +#ifndef GUAC_TELNET_H +#define GUAC_TELNET_H #include "config.h" +#include "settings.h" +#include "terminal.h" #include #include +/** + * Telnet-specific client data. + */ +typedef struct guac_telnet_client { + + /** + * Telnet connection settings. + */ + guac_telnet_settings* settings; + + /** + * The telnet client thread. + */ + pthread_t client_thread; + + /** + * The file descriptor of the socket connected to the telnet server, + * or -1 if no connection has been established. + */ + int socket_fd; + + /** + * Telnet connection, used by the telnet client thread. + */ + telnet_t* telnet; + + /** + * Whether window size should be sent when the window is resized. + */ + int naws_enabled; + + /** + * Whether all user input should be automatically echoed to the + * terminal. + */ + int echo_enabled; + + /** + * The terminal which will render all output from the telnet client. + */ + guac_terminal* term; + +} guac_telnet_client; + /** * Main telnet client thread, handling transfer of telnet output to STDOUT. */ diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c new file mode 100644 index 00000000..83db1f27 --- /dev/null +++ b/src/protocols/telnet/user.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * 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 "settings.h" +#include "telnet.h" +#include "terminal.h" +#include "user.h" + +#include +#include +#include + +#include +#include + +int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + + /* Connect via telnet if owner */ + if (user->owner) { + + /* Parse arguments into client */ + guac_telnet_settings* settings = telnet_client->settings = + guac_telnet_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(&(telnet_client->client_thread), NULL, + guac_telnet_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to start telnet client thread"); + return 1; + } + + } + + /* If not owner, synchronize with current display */ + else { + guac_terminal_dup(telnet_client->term, user, user->socket); + guac_socket_flush(user->socket); + } + + /* Set per-user event handlers */ + user->key_handler = guac_telnet_user_key_handler; + user->mouse_handler = guac_telnet_user_mouse_handler; + user->size_handler = guac_telnet_user_size_handler; + user->clipboard_handler = guac_telnet_clipboard_handler; + + return 0; + +} + diff --git a/src/protocols/telnet/user.h b/src/protocols/telnet/user.h new file mode 100644 index 00000000..ca1d717b --- /dev/null +++ b/src/protocols/telnet/user.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 Glyptodon, Inc. + * + * 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_TELNET_USER_H +#define GUAC_TELNET_USER_H + +#include "config.h" + +#include + +/** + * Handler for joining users. + */ +guac_user_join_handler guac_telnet_user_join_handler; + +#endif +