From eb5aa14a6fac9b10201a48960bacdac274c251be Mon Sep 17 00:00:00 2001 From: Jim Chen Date: Thu, 23 Aug 2018 23:36:26 -0400 Subject: [PATCH 001/175] GUACAMOLE-470: Support named colors in color-scheme configuration. When parsing named colors, treat semi-colons as string terminators, so we can properly parse named colors within the color-scheme configuration. --- src/terminal/named-colors.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/terminal/named-colors.c b/src/terminal/named-colors.c index b0e579b6..ab94153e 100644 --- a/src/terminal/named-colors.c +++ b/src/terminal/named-colors.c @@ -761,8 +761,13 @@ static int guac_terminal_named_color_search(const void* a, const void* b) { /* Skip any spaces in key (name will never have spaces) */ while (*key && isspace(*key)) key++; + /* Treat semi-colon as string terminator, to support parsing color + names within a larger string (e.g. within the terminal color-scheme + configuration string). */ + const int keyChar = (*key == ';') ? '\0' : tolower(*key); + /* Compare, ignoring case (name is already known to be lowercase) */ - int difference = tolower(*key) - *name; + int difference = keyChar - *name; if (difference) return difference; From 61a51df1b29695f6464193b18dd4f24809ef07df Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 30 Aug 2018 10:01:41 -0700 Subject: [PATCH 002/175] GUACAMOLE-622: Require guac_terminal_start() to be invoked before the terminal will render frames or accept user input. --- src/terminal/terminal.c | 29 ++++++++++++++++++++++++++--- src/terminal/terminal/terminal.h | 29 ++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 760e128f..0427ae45 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -596,6 +596,7 @@ guac_terminal* guac_terminal_create(guac_client* client, available_width = 0; guac_terminal* term = malloc(sizeof(guac_terminal)); + term->started = false; term->client = client; term->upload_path_handler = NULL; term->file_download_handler = NULL; @@ -723,6 +724,11 @@ guac_terminal* guac_terminal_create(guac_client* client, } +void guac_terminal_start(guac_terminal* term) { + term->started = true; + guac_terminal_notify(term); +} + void guac_terminal_stop(guac_terminal* term) { /* Close input pipe and set fds to invalid */ @@ -851,11 +857,13 @@ wait_complete: int guac_terminal_render_frame(guac_terminal* terminal) { + guac_client* client = terminal->client; + int wait_result; /* Wait for data to be available */ wait_result = guac_terminal_wait(terminal, 1000); - if (wait_result) { + if (wait_result || !terminal->started) { guac_timestamp frame_start = guac_timestamp_current(); @@ -867,13 +875,14 @@ int guac_terminal_render_frame(guac_terminal* terminal) { - frame_end; /* Wait again if frame remaining */ - if (frame_remaining > 0) + if (frame_remaining > 0 || !terminal->started) wait_result = guac_terminal_wait(terminal, GUAC_TERMINAL_FRAME_TIMEOUT); else break; - } while (wait_result > 0); + } while (client->state == GUAC_CLIENT_RUNNING + && (wait_result > 0 || !terminal->started)); /* Flush terminal */ guac_terminal_lock(terminal); @@ -1672,6 +1681,13 @@ int guac_terminal_send_string(guac_terminal* term, const char* data) { static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { + /* Ignore user input if terminal is not started */ + if (!term->started) { + guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input " + "while terminal has not yet started."); + return 0; + } + /* Hide mouse cursor if not already hidden */ if (term->current_cursor != GUAC_TERMINAL_CURSOR_BLANK) { term->current_cursor = GUAC_TERMINAL_CURSOR_BLANK; @@ -1845,6 +1861,13 @@ int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) { static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, int x, int y, int mask) { + /* Ignore user input if terminal is not started */ + if (!term->started) { + guac_client_log(term->client, GUAC_LOG_DEBUG, "Ignoring user input " + "while terminal has not yet started."); + return 0; + } + /* Determine which buttons were just released and pressed */ int released_mask = term->mouse_mask & ~mask; int pressed_mask = ~term->mouse_mask & mask; diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index bddf8e55..9a0145f7 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -171,6 +171,16 @@ struct guac_terminal { */ guac_client* client; + /** + * Whether user input should be handled and this terminal should render + * frames. Initially, this will be false, user input will be ignored, and + * rendering of frames will be withheld until guac_terminal_start() has + * been invoked. The data within frames will still be rendered, and text + * data received will still be handled, however actual frame boundaries + * will not be sent. + */ + bool started; + /** * The terminal render thread. */ @@ -526,7 +536,13 @@ struct guac_terminal { /** * Creates a new guac_terminal, having the given width and height, and - * rendering to the given client. + * rendering to the given client. As failover mechanisms and the Guacamole + * client implementation typically use the receipt of a "sync" message to + * denote successful connection, rendering of frames (sending of "sync") will + * be withheld until guac_terminal_start() is called, and user input will be + * ignored. The guac_terminal_start() function should be invoked only after + * either the underlying connection has truly succeeded, or until visible + * terminal output or user input is required. * * @param client * The client to which the terminal will be rendered. @@ -604,6 +620,17 @@ int guac_terminal_render_frame(guac_terminal* terminal); */ int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size); +/** + * Notifies the terminal that rendering should begin and that user input should + * now be accepted. This function must be invoked following terminal creation + * for the end of frames to be signalled with "sync" messages. Until this + * function is invoked, "sync" messages will be withheld. + * + * @param term + * The terminal to start. + */ +void guac_terminal_start(guac_terminal* term); + /** * Manually stop the terminal to forcibly unblock any pending reads/writes, * e.g. forcing guac_terminal_read_stdin() to return and cease all terminal I/O. From 0b39b0fc5fa9846ff350cc443b8f432efb4678ee Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 30 Aug 2018 10:06:20 -0700 Subject: [PATCH 003/175] GUACAMOLE-622: Implicitly invoke guac_terminal_start() if prompting is required. --- src/terminal/terminal.c | 3 +++ src/terminal/terminal/terminal.h | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 0427ae45..95e945d8 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -943,6 +943,9 @@ char* guac_terminal_prompt(guac_terminal* terminal, const char* title, int pos; char in_byte; + /* Prompting implicitly requires user input */ + guac_terminal_start(terminal); + /* Print title */ guac_terminal_printf(terminal, "%s", title); diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 9a0145f7..08094baa 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -652,7 +652,9 @@ void guac_terminal_notify(guac_terminal* terminal); /** * Reads a single line from this terminal's STDIN, storing the result in a * newly-allocated string. Input is retrieved in the same manner as - * guac_terminal_read_stdin() and the same restrictions apply. + * guac_terminal_read_stdin() and the same restrictions apply. As reading input + * naturally requires user interaction, this function will implicitly invoke + * guac_terminal_start(). * * @param terminal * The terminal to which the provided title should be output, and from From 4606607309761cca9f69dbd8e15be2376950dac3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 30 Aug 2018 10:08:30 -0700 Subject: [PATCH 004/175] GUACAMOLE-622: Start terminal for SSH only after SSH connection succeeds. --- src/protocols/ssh/ssh.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 59554fb7..0760f29c 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -335,6 +335,7 @@ void* ssh_client_thread(void* data) { /* Logged in */ guac_client_log(client, GUAC_LOG_INFO, "SSH connection successful."); + guac_terminal_start(ssh_client->term); /* Start input thread */ if (pthread_create(&(input_thread), NULL, ssh_input_thread, (void*) client)) { From 286cbf32a7a178cd6483a52983a33d486edc9129 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 1 Sep 2018 21:26:37 -0700 Subject: [PATCH 005/175] GUACAMOLE-622: Ensure connection to guacd is kept alive even if the SSH daemon is taking its time responding. Lengthy connect times due to DNS verification, PAM, etc. are not uncommon. --- src/protocols/ssh/ssh.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 0760f29c..cddb2fed 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -233,6 +233,9 @@ void* ssh_client_thread(void* data) { return NULL; } + /* Ensure connection is kept alive during lengthy connects */ + guac_socket_require_keep_alive(client->socket); + /* Open SSH session */ ssh_client->session = guac_common_ssh_create_session(client, settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval, From 1178b475dad6b8c567041dda8e3fabd34a584f75 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 1 Sep 2018 21:41:13 -0700 Subject: [PATCH 006/175] GUACAMOLE-622: Do not allow STDIN to be redirected if the terminal is not yet started. --- src/terminal/terminal-stdin-stream.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/terminal/terminal-stdin-stream.c b/src/terminal/terminal-stdin-stream.c index 1f662d67..02f5b943 100644 --- a/src/terminal/terminal-stdin-stream.c +++ b/src/terminal/terminal-stdin-stream.c @@ -103,6 +103,22 @@ static int guac_terminal_input_stream_end_handler(guac_user* user, static int __guac_terminal_send_stream(guac_terminal* term, guac_user* user, guac_stream* stream) { + /* Deny redirecting STDIN if terminal is not started */ + if (!term->started) { + + guac_user_log(user, GUAC_LOG_DEBUG, "Attempt to direct the contents " + "of an inbound stream to STDIN denied. The terminal is not " + "yet ready for input."); + + guac_protocol_send_ack(user->socket, stream, + "Terminal not yet started.", + GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT); + + guac_socket_flush(user->socket); + return 1; + + } + /* If a stream is already being used for STDIN, deny creation of * further streams */ if (term->input_stream != NULL) { From 948d1bcac86f29008a7b6caf0119107f4e846b00 Mon Sep 17 00:00:00 2001 From: netromnetrom <35839198+netromnetrom@users.noreply.github.com> Date: Tue, 11 Sep 2018 13:40:25 +0200 Subject: [PATCH 007/175] Create dk_dk_qwerty.keymap Danish keyboard layout --- src/protocols/rdp/keymaps/dk_dk_qwerty.keymap | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/protocols/rdp/keymaps/dk_dk_qwerty.keymap diff --git a/src/protocols/rdp/keymaps/dk_dk_qwerty.keymap b/src/protocols/rdp/keymaps/dk_dk_qwerty.keymap new file mode 100644 index 00000000..6295cabd --- /dev/null +++ b/src/protocols/rdp/keymaps/dk_dk_qwerty.keymap @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "dk-dk-qwerty" +freerdp "KBD_DANISH" + +# +# Basic keys +# + +map -altgr -shift 0x29 0x02..0x0C ~ "§1234567890+" +map -altgr -shift 0x10..0x1A ~ "qwertyuiopå" +map -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklæø'" +map -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x03 ~ "@" +map +altgr -shift 0x04 ~ "£" +map +altgr -shift 0x05 ~ "$" +map +altgr -shift 0x08 ~ "{" +map +altgr -shift 0x09 ~ "[" +map +altgr -shift 0x0A ~ "]" +map +altgr -shift 0x0B ~ "}" +map +altgr -shift 0x56 ~ "\" + +map +altgr -shift 0x12 ~ "€" + +map +altgr -shift 0x0D ~ "|" +map +altgr -shift 0x32 ~ "µ" + +# +# Dead keys +# + +map -altgr -shift 0x0D ~ 0xFE51 # Dead acute +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut +map -altgr +shift 0x1B ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde From 5f8c6470ffe8bcd5f6594545174744d25d7aeea7 Mon Sep 17 00:00:00 2001 From: netromnetrom <35839198+netromnetrom@users.noreply.github.com> Date: Tue, 11 Sep 2018 13:44:29 +0200 Subject: [PATCH 008/175] Update Makefile.am Danish keyboard layout --- src/protocols/rdp/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index cffabb2c..b58ddbb2 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -261,6 +261,7 @@ rdp_keymaps = \ $(srcdir)/keymaps/ja_jp_qwerty.keymap \ $(srcdir)/keymaps/pt_br_qwerty.keymap \ $(srcdir)/keymaps/sv_se_qwerty.keymap \ + $(srcdir)/keymaps/dk_dk_qwerty.keymap \ $(srcdir)/keymaps/tr_tr_qwerty.keymap _generated_keymaps.c: $(rdp_keymaps) From 442b1d5cc2a460a9f6d4797e9b1428c181aee2c4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 2 Sep 2018 14:32:05 -0700 Subject: [PATCH 009/175] GUACAMOLE-622: Start terminal for telnet only after login status is known (if login success/failure detection enabled). --- src/protocols/telnet/settings.c | 87 ++++++++++++++++++++++----- src/protocols/telnet/settings.h | 24 ++++++++ src/protocols/telnet/telnet.c | 102 +++++++++++++++++++++++++------- 3 files changed, 178 insertions(+), 35 deletions(-) diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index bef5cfae..890d5fec 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -53,6 +53,8 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "backspace", "terminal-type", "scrollback", + "login-success-regex", + "login-failure-regex", NULL }; @@ -196,12 +198,31 @@ enum TELNET_ARGS_IDX { */ IDX_SCROLLBACK, + /** + * The regular expression to use when searching for whether login was + * successful. This parameter is optional. If given, the + * "login-failure-regex" parameter must also be specified, and the first + * frame of the Guacamole connection will be withheld until login + * success/failure has been determined. + */ + IDX_LOGIN_SUCCESS_REGEX, + + /** + * The regular expression to use when searching for whether login was + * unsuccessful. This parameter is optional. If given, the + * "login-success-regex" parameter must also be specified, and the first + * frame of the Guacamole connection will be withheld until login + * success/failure has been determined. + */ + IDX_LOGIN_FAILURE_REGEX, + TELNET_ARGS_COUNT }; /** - * Compiles the given regular expression, returning NULL if compilation fails. - * The returned regex_t must be freed with regfree() AND free(). + * Compiles the given regular expression, returning NULL if compilation fails + * or of the given regular expression is NULL. The returned regex_t must be + * freed with regfree() AND free(), or with guac_telnet_regex_free(). * * @param user * The user who provided the setting associated with the given regex @@ -211,10 +232,15 @@ enum TELNET_ARGS_IDX { * The regular expression pattern to compile. * * @return - * The compiled regular expression, or NULL if compilation fails. + * The compiled regular expression, or NULL if compilation fails or NULL + * was originally provided for the pattern. */ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { + /* Nothing to compile if no pattern provided */ + if (pattern == NULL) + return NULL; + int compile_result; regex_t* regex = malloc(sizeof(regex_t)); @@ -233,6 +259,14 @@ static regex_t* guac_telnet_compile_regex(guac_user* user, char* pattern) { return regex; } +void guac_telnet_regex_free(regex_t** regex) { + if (*regex != NULL) { + regfree(*regex); + free(*regex); + *regex = NULL; + } +} + guac_telnet_settings* guac_telnet_parse_args(guac_user* user, int argc, const char** argv) { @@ -256,7 +290,7 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_USERNAME, NULL); - /* Read username regex only if password is specified */ + /* Read username regex only if username 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, @@ -275,6 +309,35 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX)); } + /* Read optional login success detection regex */ + settings->login_success_regex = guac_telnet_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_LOGIN_SUCCESS_REGEX, NULL)); + + /* Read optional login failure detection regex */ + settings->login_failure_regex = guac_telnet_compile_regex(user, + guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_LOGIN_FAILURE_REGEX, NULL)); + + /* Both login success and login failure regexes must be provided if either + * is present at all */ + if (settings->login_success_regex != NULL + && settings->login_failure_regex == NULL) { + guac_telnet_regex_free(&settings->login_success_regex); + guac_user_log(user, GUAC_LOG_WARNING, "Ignoring provided value for " + "\"%s\" as \"%s\" must also be provided.", + GUAC_TELNET_CLIENT_ARGS[IDX_LOGIN_SUCCESS_REGEX], + GUAC_TELNET_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX]); + } + else if (settings->login_failure_regex != NULL + && settings->login_success_regex == NULL) { + guac_telnet_regex_free(&settings->login_failure_regex); + guac_user_log(user, GUAC_LOG_WARNING, "Ignoring provided value for " + "\"%s\" as \"%s\" must also be provided.", + GUAC_TELNET_CLIENT_ARGS[IDX_LOGIN_FAILURE_REGEX], + GUAC_TELNET_CLIENT_ARGS[IDX_LOGIN_SUCCESS_REGEX]); + } + /* Read-only mode */ settings->read_only = guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, @@ -380,17 +443,11 @@ void guac_telnet_settings_free(guac_telnet_settings* settings) { 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 various regexes */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + guac_telnet_regex_free(&settings->login_success_regex); + guac_telnet_regex_free(&settings->login_failure_regex); /* Free display preferences */ free(settings->font_name); diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 83ec4313..86302b77 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -117,6 +117,20 @@ typedef struct guac_telnet_settings { */ regex_t* password_regex; + /** + * The regular expression to use when searching for whether login was + * successful. If no such regex is specified, or if no login failure regex + * was specified, this will be NULL. + */ + regex_t* login_success_regex; + + /** + * The regular expression to use when searching for whether login failed. + * If no such regex is specified, or if no login success regex was + * specified, this will be NULL. + */ + regex_t* login_failure_regex; + /** * Whether this connection is read-only, and user input should be dropped. */ @@ -253,6 +267,16 @@ typedef struct guac_telnet_settings { guac_telnet_settings* guac_telnet_parse_args(guac_user* user, int argc, const char** argv); +/** + * Frees the regex pointed to by the given pointer, assigning the value NULL to + * that pointer once the regex is freed. If the pointer already contains NULL, + * this function has no effect. + * + * @param regex + * The address of the pointer to the regex that should be freed. + */ +void guac_telnet_regex_free(regex_t** regex); + /** * Frees the given guac_telnet_settings object, having been previously * allocated via guac_telnet_parse_args(). diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index c9a636c2..36e4cac4 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -84,10 +85,32 @@ static int __guac_telnet_write_all(int fd, const char* buffer, int size) { /** * Searches for a line matching the stored password regex, appending the given * buffer to the internal pattern matching buffer. The internal pattern match - * buffer is cleared whenever a newline is read. Returns TRUE if a match is found and the - * value is sent. + * buffer is cleared whenever a newline is read. Returns true if a match is + * found and the value is sent. An enter keypress is automatically sent after + * the value is sent. + * + * @param client + * The guac_client associated with the telnet session. + * + * @param regex + * The regex to search for within the output of the telnet session + * associated with the given client. + * + * @param value + * The string value to send once a match is found, or NULL if no value + * should be sent. + * + * @param buffer + * The buffer of received data to search through. + * + * @param size + * The size of the given buffer, in bytes. + * + * @return + * true if a match is found, false otherwise. */ -static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, char* value, const char* buffer, int size) { +static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, + char* value, const char* buffer, int size) { static char line_buffer[1024] = {0}; static int length = 0; @@ -123,16 +146,17 @@ 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(telnet_client->term, value); - guac_terminal_send_key(telnet_client->term, 0xFF0D, 1); - guac_terminal_send_key(telnet_client->term, 0xFF0D, 0); + if (value != NULL) { + guac_terminal_send_string(telnet_client->term, value); + guac_terminal_send_string(telnet_client->term, "\x0D"); + } /* Stop searching for prompt */ - return TRUE; + return true; } - return FALSE; + return false; } /** @@ -158,9 +182,7 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, settings->username_regex, settings->username, event->data.buffer, event->data.size)) { guac_client_log(client, GUAC_LOG_DEBUG, "Username sent"); - regfree(settings->username_regex); - free(settings->username_regex); - settings->username_regex = NULL; + guac_telnet_regex_free(&settings->username_regex); } } @@ -172,18 +194,52 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, guac_client_log(client, GUAC_LOG_DEBUG, "Password sent"); - /* Do not continue searching for username once password is sent */ - if (settings->username_regex != NULL) { - regfree(settings->username_regex); - free(settings->username_regex); - settings->username_regex = NULL; - } + /* Do not continue searching for username/password once password is sent */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); - regfree(settings->password_regex); - free(settings->password_regex); - settings->password_regex = NULL; } } + + /* Continue search for login success */ + if (settings->login_success_regex != NULL) { + if (__guac_telnet_regex_search(client, + settings->login_success_regex, NULL, + event->data.buffer, event->data.size)) { + + /* Allow terminal to render now that login has been deemed successful */ + guac_client_log(client, GUAC_LOG_DEBUG, "Login successful"); + guac_terminal_start(telnet_client->term); + + /* Stop all searches */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + guac_telnet_regex_free(&settings->login_success_regex); + guac_telnet_regex_free(&settings->login_failure_regex); + + } + } + + /* Continue search for login failure */ + if (settings->login_failure_regex != NULL) { + if (__guac_telnet_regex_search(client, + settings->login_failure_regex, NULL, + event->data.buffer, event->data.size)) { + + /* Advise that login has failed and connection should be closed */ + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Login failed"); + + /* Stop all searches */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + guac_telnet_regex_free(&settings->login_success_regex); + guac_telnet_regex_free(&settings->login_failure_regex); + + } + } + break; /* Data destined for remote end */ @@ -508,6 +564,12 @@ void* guac_telnet_client_thread(void* data) { /* Logged in */ guac_client_log(client, GUAC_LOG_INFO, "Telnet connection successful."); + /* Allow terminal to render if login success/failure detection is not + * enabled */ + if (settings->login_success_regex == NULL + && settings->login_failure_regex == NULL) + guac_terminal_start(telnet_client->term); + /* Start input thread */ if (pthread_create(&(input_thread), NULL, __guac_telnet_input_thread, (void*) client)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); From 462d494ed865a0fd47ef6cb22d23089d12f2b1ac Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 2 Sep 2018 22:59:18 -0700 Subject: [PATCH 010/175] GUACAMOLE-622: Match each line against all regexes. --- src/protocols/telnet/telnet.c | 252 +++++++++++++++++++--------------- 1 file changed, 143 insertions(+), 109 deletions(-) diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 36e4cac4..17abd681 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -83,65 +83,31 @@ static int __guac_telnet_write_all(int fd, const char* buffer, int size) { } /** - * Searches for a line matching the stored password regex, appending the given - * buffer to the internal pattern matching buffer. The internal pattern match - * buffer is cleared whenever a newline is read. Returns true if a match is - * found and the value is sent. An enter keypress is automatically sent after - * the value is sent. + * Matches the given line against the given regex, returning true and sending + * the given value if a match is found. An enter keypress is automatically + * sent after the value is sent. * * @param client * The guac_client associated with the telnet session. * * @param regex - * The regex to search for within the output of the telnet session - * associated with the given client. + * The regex to search for within the given line buffer. * * @param value - * The string value to send once a match is found, or NULL if no value - * should be sent. + * The string value to send through STDIN of the telnet session if a + * match is found, or NULL if no value should be sent. * - * @param buffer - * The buffer of received data to search through. - * - * @param size - * The size of the given buffer, in bytes. + * @param line_buffer + * The line of character data to test. * * @return * true if a match is found, false otherwise. */ -static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, - char* value, const char* buffer, int size) { - - static char line_buffer[1024] = {0}; - static int length = 0; +static bool guac_telnet_regex_exec(guac_client* client, regex_t* regex, + const char* value, const char* line_buffer) { guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; - int i; - const char* current; - - /* Ensure line buffer contains only the most recent line */ - current = buffer; - for (i = 0; i < size; i++) { - - /* Reset line buffer and shift input buffer for each newline */ - if (*(current++) == '\n') { - length = 0; - buffer += i; - size -= i; - i = 0; - } - } - - /* Truncate if necessary */ - if (size + length + 1 > sizeof(line_buffer)) - size = sizeof(line_buffer) - length - 1; - - /* Append to line */ - memcpy(&(line_buffer[length]), buffer, size); - length += size; - line_buffer[length] = '\0'; - /* Send value upon match */ if (regexec(regex, line_buffer, 0, NULL, 0) == 0) { @@ -157,6 +123,138 @@ static bool __guac_telnet_regex_search(guac_client* client, regex_t* regex, } return false; + +} + +/** + * Matches the given line against the various stored regexes, automatically + * sending the configured username, password, or reporting login + * success/failure depending on context. If no search is in progress, either + * because no regexes have been defined or because all applicable searches have + * completed, this function has no effect. + * + * @param client + * The guac_client associated with the telnet session. + * + * @param line_buffer + * The line of character data to test. + */ +static void guac_telnet_search_line(guac_client* client, const char* line_buffer) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_telnet_settings* settings = telnet_client->settings; + + /* Continue search for username prompt */ + if (settings->username_regex != NULL) { + if (guac_telnet_regex_exec(client, settings->username_regex, + settings->username, line_buffer)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Username sent"); + guac_telnet_regex_free(&settings->username_regex); + } + } + + /* Continue search for password prompt */ + if (settings->password_regex != NULL) { + if (guac_telnet_regex_exec(client, settings->password_regex, + settings->password, line_buffer)) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Password sent"); + + /* Do not continue searching for username/password once password is sent */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + + } + } + + /* Continue search for login success */ + if (settings->login_success_regex != NULL) { + if (guac_telnet_regex_exec(client, settings->login_success_regex, + NULL, line_buffer)) { + + /* Allow terminal to render now that login has been deemed successful */ + guac_client_log(client, GUAC_LOG_DEBUG, "Login successful"); + guac_terminal_start(telnet_client->term); + + /* Stop all searches */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + guac_telnet_regex_free(&settings->login_success_regex); + guac_telnet_regex_free(&settings->login_failure_regex); + + } + } + + /* Continue search for login failure */ + if (settings->login_failure_regex != NULL) { + if (guac_telnet_regex_exec(client, settings->login_failure_regex, + NULL, line_buffer)) { + + /* Advise that login has failed and connection should be closed */ + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Login failed"); + + /* Stop all searches */ + guac_telnet_regex_free(&settings->username_regex); + guac_telnet_regex_free(&settings->password_regex); + guac_telnet_regex_free(&settings->login_success_regex); + guac_telnet_regex_free(&settings->login_failure_regex); + + } + } + +} + +/** + * Searches for a line matching the various stored regexes, automatically + * sending the configured username, password, or reporting login + * success/failure depending on context. If no search is in progress, either + * because no regexes have been defined or because all applicable searches + * have completed, this function has no effect. + * + * @param client + * The guac_client associated with the telnet session. + * + * @param buffer + * The buffer of received data to search through. + * + * @param size + * The size of the given buffer, in bytes. + */ +static void guac_telnet_search(guac_client* client, const char* buffer, int size) { + + static char line_buffer[1024] = {0}; + static int length = 0; + + /* Append all characters in buffer to current line */ + const char* current = buffer; + for (int i = 0; i < size; i++) { + + char c = *(current++); + + /* Attempt pattern match and clear buffer upon reading newline */ + if (c == '\n') { + if (length > 0) { + line_buffer[length] = '\0'; + guac_telnet_search_line(client, line_buffer); + length = 0; + } + } + + /* Append all non-newline characters to line buffer as long as space + * remains */ + else if (length < sizeof(line_buffer) - 1) + line_buffer[length++] = c; + + } + + /* Attempt pattern match if an unfinished line remains (may be a prompt) */ + if (length > 0) { + line_buffer[length] = '\0'; + guac_telnet_search_line(client, line_buffer); + } + } /** @@ -175,71 +273,7 @@ static void __guac_telnet_event_handler(telnet_t* telnet, telnet_event_t* event, /* Terminal output received */ case TELNET_EV_DATA: guac_terminal_write(telnet_client->term, event->data.buffer, event->data.size); - - /* Continue search for username prompt */ - 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"); - guac_telnet_regex_free(&settings->username_regex); - } - } - - /* Continue search for password prompt */ - 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/password once password is sent */ - guac_telnet_regex_free(&settings->username_regex); - guac_telnet_regex_free(&settings->password_regex); - - } - } - - /* Continue search for login success */ - if (settings->login_success_regex != NULL) { - if (__guac_telnet_regex_search(client, - settings->login_success_regex, NULL, - event->data.buffer, event->data.size)) { - - /* Allow terminal to render now that login has been deemed successful */ - guac_client_log(client, GUAC_LOG_DEBUG, "Login successful"); - guac_terminal_start(telnet_client->term); - - /* Stop all searches */ - guac_telnet_regex_free(&settings->username_regex); - guac_telnet_regex_free(&settings->password_regex); - guac_telnet_regex_free(&settings->login_success_regex); - guac_telnet_regex_free(&settings->login_failure_regex); - - } - } - - /* Continue search for login failure */ - if (settings->login_failure_regex != NULL) { - if (__guac_telnet_regex_search(client, - settings->login_failure_regex, NULL, - event->data.buffer, event->data.size)) { - - /* Advise that login has failed and connection should be closed */ - guac_client_abort(client, - GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, - "Login failed"); - - /* Stop all searches */ - guac_telnet_regex_free(&settings->username_regex); - guac_telnet_regex_free(&settings->password_regex); - guac_telnet_regex_free(&settings->login_success_regex); - guac_telnet_regex_free(&settings->login_failure_regex); - - } - } - + guac_telnet_search(client, event->data.buffer, event->data.size); break; /* Data destined for remote end */ From 622a849bae04a512e5c9fc1e6e782fd1bb77953a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 21 Sep 2018 16:25:11 -0700 Subject: [PATCH 011/175] GUACAMOLE-628: Correct RDP scancode mapping for right control key. --- src/protocols/rdp/keymaps/base.keymap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/keymaps/base.keymap b/src/protocols/rdp/keymaps/base.keymap index 4e49f741..c55a37fc 100644 --- a/src/protocols/rdp/keymaps/base.keymap +++ b/src/protocols/rdp/keymaps/base.keymap @@ -81,7 +81,7 @@ map 0x58 ~ 0xffc9 # F12 map 0x2A ~ 0xffe1 # Shift_L map 0x36 ~ 0xffe2 # Shift_R map 0x1D ~ 0xffe3 # Control_L -map 0x9D ~ 0xffe4 # Control_R +map +ext 0x1D ~ 0xffe4 # Control_R map 0x38 ~ 0xffe9 # Alt_L map +ext 0x38 ~ 0xffea # Alt_R map +ext 0x5B ~ 0xffeb # Super_L From 8456c050ea339be1daae82d8677d0a9a767aa80c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 23 Sep 2018 22:39:31 -0700 Subject: [PATCH 012/175] GUACAMOLE-629: Add support for updating connection parameters of in-progress connections. --- src/libguac/guacamole/user-fntypes.h | 30 ++++++++++++++++++++++++++++ src/libguac/guacamole/user.h | 21 +++++++++++++++++++ src/libguac/user-handlers.c | 25 +++++++++++++++++++++++ src/libguac/user-handlers.h | 7 +++++++ 4 files changed, 83 insertions(+) diff --git a/src/libguac/guacamole/user-fntypes.h b/src/libguac/guacamole/user-fntypes.h index 13256c56..d8689322 100644 --- a/src/libguac/guacamole/user-fntypes.h +++ b/src/libguac/guacamole/user-fntypes.h @@ -237,6 +237,36 @@ typedef int guac_user_file_handler(guac_user* user, guac_stream* stream, typedef int guac_user_pipe_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name); +/** + * Handler for Guacamole argument value (argv) streams received from a user. + * Argument value streams are real-time revisions to the connection parameters + * of an in-progress connection. Each such argument value stream begins when + * the user sends a "argv" instruction. To handle received data along this + * stream, implementations of this handler must assign blob and end handlers to + * the given stream object. + * + * @param user + * The user that opened the argument value stream. + * + * @param stream + * The stream object allocated by libguac to represent the argument value + * stream opened by the user. + * + * @param mimetype + * The mimetype of the data that will be sent along the stream. + * + * @param name + * The name of the connection parameter being updated. It is up to the + * implementation of this handler to decide whether and how to update a + * connection parameter. + * + * @return + * Zero if the opening of the argument value stream has been handled + * successfully, or non-zero if an error occurs. + */ +typedef int guac_user_argv_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name); + /** * Handler for Guacamole stream blobs. Each blob originates from a "blob" * instruction which was associated with a previously-created stream. diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index f89f8077..792c7421 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -475,6 +475,27 @@ struct guac_user { */ guac_user_audio_handler* audio_handler; + /** + * Handler for argv events (updates to the connection parameters of an + * in-progress connection) sent by the Guacamole web-client. + * + * The handler takes a guac_stream which contains the stream index and + * will persist through the duration of the transfer, the mimetype of + * the data being transferred, and the argument (connection parameter) + * name. + * + * Example: + * @code + * int argv_handler(guac_user* user, guac_stream* stream, + * char* mimetype, char* name); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->argv_handler = argv_handler; + * } + * @endcode + */ + guac_user_argv_handler* argv_handler; + }; /** diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index 0e8ed38b..b84dc721 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -49,6 +49,7 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = { {"get", __guac_handle_get}, {"put", __guac_handle_put}, {"audio", __guac_handle_audio}, + {"argv", __guac_handle_argv}, {NULL, NULL} }; @@ -382,6 +383,30 @@ int __guac_handle_pipe(guac_user* user, int argc, char** argv) { return 0; } +int __guac_handle_argv(guac_user* user, int argc, char** argv) { + + /* Pull corresponding stream */ + int stream_index = atoi(argv[0]); + guac_stream* stream = __init_input_stream(user, stream_index); + if (stream == NULL) + return 0; + + /* If supported, call handler */ + if (user->argv_handler) + return user->argv_handler( + user, + stream, + argv[1], /* mimetype */ + argv[2] /* name */ + ); + + /* Otherwise, abort */ + guac_protocol_send_ack(user->socket, stream, + "Reconfiguring in-progress connections unsupported", + GUAC_PROTOCOL_STATUS_UNSUPPORTED); + return 0; +} + int __guac_handle_ack(guac_user* user, int argc, char** argv) { guac_stream* stream; diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index 7a1b623a..eedeba15 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -120,6 +120,13 @@ __guac_instruction_handler __guac_handle_file; */ __guac_instruction_handler __guac_handle_pipe; +/** + * Internal initial handler for the argv instruction. When a argv instruction + * is received, this handler will be called. The client's argv handler will + * be invoked if defined. + */ +__guac_instruction_handler __guac_handle_argv; + /** * Internal initial handler for the ack instruction. When a ack instruction * is received, this handler will be called. The client's ack handler will From 45e8503ead5aa90ee8f53ddf43257c019069653d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 17 May 2016 20:49:51 -0700 Subject: [PATCH 013/175] GUACAMOLE-632: Dynamically scale JPEG/WebP quality depending on measured processing lag. --- src/common/surface.c | 48 +++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/common/surface.c b/src/common/surface.c index 8c690b75..c31dfbf0 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -78,13 +78,6 @@ #define cairo_format_stride_for_width(format, width) (width*4) #endif -/** - * The JPEG image quality ('quantization') setting to use. Range 0-100 where - * 100 is the highest quality/largest file size, and 0 is the lowest - * quality/smallest file size. - */ -#define GUAC_SURFACE_JPEG_IMAGE_QUALITY 90 - /** * The framerate which, if exceeded, indicates that JPEG is preferred. */ @@ -96,13 +89,6 @@ */ #define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096 -/** - * The WebP image quality ('quantization') setting to use. Range 0-100 where - * 100 is the highest quality/largest file size, and 0 is the lowest - * quality/smallest file size. - */ -#define GUAC_SURFACE_WEBP_IMAGE_QUALITY 90 - /** * The JPEG compression min block size. This defines the optimal rectangle block * size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to @@ -1666,6 +1652,36 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface, } +/** + * Returns an appropriate quality between 0 and 100 for lossy encoding + * depending on the current processing lag calculated for the given client. + * + * @param client + * The client for which the lossy quality is being calculated. + * + * @return + * A value between 0 and 100 inclusive which seems appropriate for the + * client based on lag measurements. + */ +static int guac_common_surface_suggest_quality(guac_client* client) { + + int lag = guac_client_get_processing_lag(client); + + /* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */ + int quality = 90 - (lag - 20); + + /* Do not exceed 90 for quality */ + if (quality > 90) + return 90; + + /* Do not go below 30 for quality */ + if (quality < 30) + return 30; + + return quality; + +} + /** * Flushes the bitmap update currently described by the dirty rectangle within * the given surface directly via an "img" instruction as JPEG data. The @@ -1702,7 +1718,7 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) { /* Send JPEG for rect */ guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, rect, - GUAC_SURFACE_JPEG_IMAGE_QUALITY); + guac_common_surface_suggest_quality(surface->client)); cairo_surface_destroy(rect); surface->realized = 1; @@ -1764,7 +1780,7 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface, /* Send WebP for rect */ guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer, surface->dirty_rect.x, surface->dirty_rect.y, rect, - GUAC_SURFACE_WEBP_IMAGE_QUALITY, 0); + guac_common_surface_suggest_quality(surface->client), 0); cairo_surface_destroy(rect); surface->realized = 1; From b8bd0e4c6a63995c18050fa4f88fa09b97f7a90c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 9 Sep 2018 20:03:40 -0700 Subject: [PATCH 014/175] GUACAMOLE-623: Add base skeleton for Kubernetes protocol support. --- Makefile.am | 4 + configure.ac | 60 ++++- src/protocols/kubernetes/Makefile.am | 57 ++++ src/protocols/kubernetes/client.c | 91 +++++++ src/protocols/kubernetes/client.h | 41 +++ src/protocols/kubernetes/clipboard.c | 67 +++++ src/protocols/kubernetes/clipboard.h | 43 +++ src/protocols/kubernetes/input.c | 95 +++++++ src/protocols/kubernetes/input.h | 46 ++++ src/protocols/kubernetes/kubernetes.c | 136 ++++++++++ src/protocols/kubernetes/kubernetes.h | 71 +++++ src/protocols/kubernetes/pipe.c | 51 ++++ src/protocols/kubernetes/pipe.h | 42 +++ src/protocols/kubernetes/settings.c | 366 ++++++++++++++++++++++++++ src/protocols/kubernetes/settings.h | 256 ++++++++++++++++++ src/protocols/kubernetes/user.c | 116 ++++++++ src/protocols/kubernetes/user.h | 38 +++ 17 files changed, 1572 insertions(+), 8 deletions(-) create mode 100644 src/protocols/kubernetes/Makefile.am create mode 100644 src/protocols/kubernetes/client.c create mode 100644 src/protocols/kubernetes/client.h create mode 100644 src/protocols/kubernetes/clipboard.c create mode 100644 src/protocols/kubernetes/clipboard.h create mode 100644 src/protocols/kubernetes/input.c create mode 100644 src/protocols/kubernetes/input.h create mode 100644 src/protocols/kubernetes/kubernetes.c create mode 100644 src/protocols/kubernetes/kubernetes.h create mode 100644 src/protocols/kubernetes/pipe.c create mode 100644 src/protocols/kubernetes/pipe.h create mode 100644 src/protocols/kubernetes/settings.c create mode 100644 src/protocols/kubernetes/settings.h create mode 100644 src/protocols/kubernetes/user.c create mode 100644 src/protocols/kubernetes/user.h diff --git a/Makefile.am b/Makefile.am index e9233760..91c8abec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,6 +52,10 @@ if ENABLE_PULSE SUBDIRS += src/pulse endif +if ENABLE_KUBERNETES +SUBDIRS += src/protocols/kubernetes +endif + if ENABLE_RDP SUBDIRS += src/protocols/rdp endif diff --git a/configure.ac b/configure.ac index 6b20c974..ae783244 100644 --- a/configure.ac +++ b/configure.ac @@ -1172,6 +1172,46 @@ fi AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"]) AC_SUBST(WEBP_LIBS) +# +# libwebsockets +# + +have_libwebsockets=disabled +WEBSOCKETS_LIBS= +AC_ARG_WITH([websockets], + [AS_HELP_STRING([--with-websockets], + [support WebSockets @<:@default=check@:>@])], + [], + [with_websockets=check]) + +if test "x$with_websockets" != "xno" +then + have_libwebsockets=yes + AC_CHECK_LIB([websockets], + [lws_create_context], + [WEBSOCKETS_LIBS="$WEBSOCKETS_LIBS -lwebsockets"], + [have_libwebsockets=no]) +fi + +AM_CONDITIONAL([ENABLE_WEBSOCKETS], + [test "x${have_libwebsockets}" = "xyes"]) + +AC_SUBST(WEBSOCKETS_LIBS) + +# +# Kubernetes +# + +AC_ARG_ENABLE([kubernetes], + [AS_HELP_STRING([--disable-kubernetes], + [do not build support for attaching to Kubernetes pods])], + [], + [enable_kubernetes=yes]) + +AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \ + -a "x${have_libwebsockets}" = "xyes" \ + -a "x${have_terminal}" = "xyes"]) + # # guacd # @@ -1230,6 +1270,7 @@ AC_CONFIG_FILES([Makefile src/guaclog/Makefile src/guaclog/man/guaclog.1 src/pulse/Makefile + src/protocols/kubernetes/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile @@ -1240,10 +1281,11 @@ AC_OUTPUT # Protocol build status # -AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) -AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) -AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) -AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) +AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no]) +AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) +AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) +AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) +AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) # # Service / tool build status @@ -1287,15 +1329,17 @@ $PACKAGE_NAME version $PACKAGE_VERSION libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} + libwebsockets ....... ${have_websockets} libwebp ............. ${have_webp} wsock32 ............. ${have_winsock} Protocol support: - RDP ....... ${build_rdp} - SSH ....... ${build_ssh} - Telnet .... ${build_telnet} - VNC ....... ${build_vnc} + Kubernetes .... ${build_kubernetes} + RDP ........... ${build_rdp} + SSH ........... ${build_ssh} + Telnet ........ ${build_telnet} + VNC ........... ${build_vnc} Services / tools: diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am new file mode 100644 index 00000000..d864967f --- /dev/null +++ b/src/protocols/kubernetes/Makefile.am @@ -0,0 +1,57 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libguac-client-kubernetes.la + +libguac_client_kubernetes_la_SOURCES = \ + client.c \ + clipboard.c \ + input.c \ + pipe.c \ + settings.c \ + kubernetes.c \ + user.c + +noinst_HEADERS = \ + client.h \ + clipboard.h \ + input.h \ + pipe.h \ + settings.h \ + kubernetes.h \ + user.h + +libguac_client_kubernetes_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @LIBGUAC_INCLUDE@ \ + @TERMINAL_INCLUDE@ + +libguac_client_kubernetes_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ + +libguac_client_kubernetes_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ \ + @WEBSOCKETS_LIBS@ + diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c new file mode 100644 index 00000000..1b5d175d --- /dev/null +++ b/src/protocols/kubernetes/client.c @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "client.h" +#include "common/recording.h" +#include "kubernetes.h" +#include "settings.h" +#include "terminal/terminal.h" +#include "user.h" + +#include +#include +#include +#include +#include + +#include + +int guac_client_init(guac_client* client) { + + /* Set client args */ + client->args = GUAC_KUBERNETES_CLIENT_ARGS; + + /* Allocate client instance data */ + guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client)); + client->data = kubernetes_client; + + /* Init clipboard */ + kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH); + + /* Set handlers */ + client->join_handler = guac_kubernetes_user_join_handler; + client->free_handler = guac_kubernetes_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."); + } + + /* Success */ + return 0; + +} + +int guac_kubernetes_client_free_handler(guac_client* client) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Clean up recording, if in progress */ + if (kubernetes_client->recording != NULL) + guac_common_recording_free(kubernetes_client->recording); + + /* Kill terminal */ + guac_terminal_free(kubernetes_client->term); + + /* TODO: Wait for and free WebSocket session, if connected */ + /*if (kubernetes_client->websocket != NULL) { + pthread_join(kubernetes_client->client_thread, NULL); + }*/ + + /* Free settings */ + if (kubernetes_client->settings != NULL) + guac_kubernetes_settings_free(kubernetes_client->settings); + + guac_common_clipboard_free(kubernetes_client->clipboard); + free(kubernetes_client); + return 0; + +} + diff --git a/src/protocols/kubernetes/client.h b/src/protocols/kubernetes/client.h new file mode 100644 index 00000000..2e96d109 --- /dev/null +++ b/src/protocols/kubernetes/client.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_CLIENT_H +#define GUAC_KUBERNETES_CLIENT_H + +#include "config.h" +#include "terminal/terminal.h" + +#include +#include + +/** + * The maximum number of bytes to allow within the clipboard. + */ +#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144 + +/** + * Free handler. Required by libguac and called when the guac_client is + * disconnected and must be cleaned up. + */ +guac_client_free_handler guac_kubernetes_client_free_handler; + +#endif + diff --git a/src/protocols/kubernetes/clipboard.c b/src/protocols/kubernetes/clipboard.c new file mode 100644 index 00000000..87a34b04 --- /dev/null +++ b/src/protocols/kubernetes/clipboard.c @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "clipboard.h" +#include "common/clipboard.h" +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include +#include + +int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Clear clipboard and prepare for new data */ + guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype); + + /* Set handlers for clipboard stream */ + stream->blob_handler = guac_kubernetes_clipboard_blob_handler; + stream->end_handler = guac_kubernetes_clipboard_end_handler; + + return 0; +} + +int guac_kubernetes_clipboard_blob_handler(guac_user* user, + guac_stream* stream, void* data, int length) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Append new data */ + guac_common_clipboard_append(kubernetes_client->clipboard, data, length); + + return 0; +} + +int guac_kubernetes_clipboard_end_handler(guac_user* user, + guac_stream* stream) { + + /* Nothing to do - clipboard is implemented within client */ + + return 0; +} + diff --git a/src/protocols/kubernetes/clipboard.h b/src/protocols/kubernetes/clipboard.h new file mode 100644 index 00000000..009219cf --- /dev/null +++ b/src/protocols/kubernetes/clipboard.h @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_CLIPBOARD_H +#define GUAC_KUBERNETES_CLIPBOARD_H + +#include "config.h" + +#include + +/** + * Handler for inbound clipboard streams. + */ +guac_user_clipboard_handler guac_kubernetes_clipboard_handler; + +/** + * Handler for data received along clipboard streams. + */ +guac_user_blob_handler guac_kubernetes_clipboard_blob_handler; + +/** + * Handler for end-of-stream related to clipboard. + */ +guac_user_end_handler guac_kubernetes_clipboard_end_handler; + +#endif + diff --git a/src/protocols/kubernetes/input.c b/src/protocols/kubernetes/input.c new file mode 100644 index 00000000..9bf5b717 --- /dev/null +++ b/src/protocols/kubernetes/input.c @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "common/recording.h" +#include "kubernetes.h" +#include "input.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include + +int guac_kubernetes_user_mouse_handler(guac_user* user, + int x, int y, int mask) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Skip if terminal not yet ready */ + guac_terminal* term = kubernetes_client->term; + if (term == NULL) + return 0; + + /* Report mouse position within recording */ + if (kubernetes_client->recording != NULL) + guac_common_recording_report_mouse(kubernetes_client->recording, x, y, + mask); + + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + +} + +int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Report key state within recording */ + if (kubernetes_client->recording != NULL) + guac_common_recording_report_key(kubernetes_client->recording, + keysym, pressed); + + /* Skip if terminal not yet ready */ + guac_terminal* term = kubernetes_client->term; + if (term == NULL) + return 0; + + guac_terminal_send_key(term, keysym, pressed); + return 0; + +} + +int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) { + + /* Get terminal */ + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Skip if terminal not yet ready */ + guac_terminal* terminal = kubernetes_client->term; + if (terminal == NULL) + return 0; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + /* TODO: Update Kubernetes terminal window size if connected */ + + return 0; +} + diff --git a/src/protocols/kubernetes/input.h b/src/protocols/kubernetes/input.h new file mode 100644 index 00000000..ac65835c --- /dev/null +++ b/src/protocols/kubernetes/input.h @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_INPUT_H +#define GUAC_KUBERNETES_INPUT_H + +#include "config.h" + +#include + +/** + * Handler for key events. Required by libguac and called whenever key events + * are received. + */ +guac_user_key_handler guac_kubernetes_user_key_handler; + +/** + * Handler for mouse events. Required by libguac and called whenever mouse + * events are received. + */ +guac_user_mouse_handler guac_kubernetes_user_mouse_handler; + +/** + * Handler for size events. Required by libguac and called whenever the remote + * display (window) is resized. + */ +guac_user_size_handler guac_kubernetes_user_size_handler; + +#endif + diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c new file mode 100644 index 00000000..231d78d3 --- /dev/null +++ b/src/protocols/kubernetes/kubernetes.c @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "common/recording.h" +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Input thread, started by the main Kubernetes client thread. This thread + * continuously reads from the terminal's STDIN and transfers all read + * data to the Kubernetes connection. + * + * @param data + * The current guac_client instance. + * + * @return + * Always NULL. + */ +static void* guac_kubernetes_input_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + char buffer[8192]; + int bytes_read; + + /* Write all data read */ + while ((bytes_read = guac_terminal_read_stdin(kubernetes_client->term, buffer, sizeof(buffer))) > 0) { + + /* TODO: Send to Kubernetes */ + guac_terminal_write(kubernetes_client->term, buffer, bytes_read); + + } + + return NULL; + +} + +void* guac_kubernetes_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + guac_kubernetes_settings* settings = kubernetes_client->settings; + + pthread_t input_thread; + + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + kubernetes_client->recording = guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path, + !settings->recording_exclude_output, + !settings->recording_exclude_mouse, + settings->recording_include_keys); + } + + /* Create terminal */ + kubernetes_client->term = guac_terminal_create(client, + kubernetes_client->clipboard, + settings->max_scrollback, settings->font_name, settings->font_size, + settings->resolution, settings->width, settings->height, + settings->color_scheme, settings->backspace); + + /* Fail if terminal init failed */ + if (kubernetes_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(kubernetes_client->term, + settings->typescript_path, + settings->typescript_name, + settings->create_typescript_path); + } + + /* TODO: Open WebSocket connection to Kubernetes */ + + /* Logged in */ + guac_client_log(client, GUAC_LOG_INFO, + "Kubernetes connection successful."); + + /* Start input thread */ + if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); + return NULL; + } + + /* TODO: While data available, write to terminal */ + + /* Kill client and Wait for input thread to die */ + guac_client_stop(client); + pthread_join(input_thread, NULL); + + guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended."); + return NULL; + +} + diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h new file mode 100644 index 00000000..f8035ae5 --- /dev/null +++ b/src/protocols/kubernetes/kubernetes.h @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_H +#define GUAC_KUBERNETES_H + +#include "config.h" +#include "common/clipboard.h" +#include "common/recording.h" +#include "settings.h" +#include "terminal/terminal.h" + +#include + +/** + * Kubernetes-specific client data. + */ +typedef struct guac_kubernetes_client { + + /** + * Kubernetes connection settings. + */ + guac_kubernetes_settings* settings; + + /** + * The Kubernetes client thread. + */ + pthread_t client_thread; + + /** + * The current clipboard contents. + */ + guac_common_clipboard* clipboard; + + /** + * The terminal which will render all output from the Kubernetes pod. + */ + guac_terminal* term; + + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + +} guac_kubernetes_client; + +/** + * Main Kubernetes client thread, handling transfer of STDOUT/STDERR of an + * attached Kubernetes pod to STDOUT of the terminal. + */ +void* guac_kubernetes_client_thread(void* data); + +#endif + diff --git a/src/protocols/kubernetes/pipe.c b/src/protocols/kubernetes/pipe.c new file mode 100644 index 00000000..242105b1 --- /dev/null +++ b/src/protocols/kubernetes/pipe.c @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "kubernetes.h" +#include "pipe.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include + +int guac_kubernetes_pipe_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Redirect STDIN if pipe has required name */ + if (strcmp(name, GUAC_KUBERNETES_STDIN_PIPE_NAME) == 0) { + guac_terminal_send_stream(kubernetes_client->term, user, stream); + return 0; + } + + /* No other inbound pipe streams are supported */ + guac_protocol_send_ack(user->socket, stream, "No such input stream.", + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/kubernetes/pipe.h b/src/protocols/kubernetes/pipe.h new file mode 100644 index 00000000..7acae3cb --- /dev/null +++ b/src/protocols/kubernetes/pipe.h @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_KUBERNETES_PIPE_H +#define GUAC_KUBERNETES_PIPE_H + +#include "config.h" + +#include + +/** + * The name reserved for the inbound pipe stream which forces the terminal + * emulator's STDIN to be received from the pipe. + */ +#define GUAC_KUBERNETES_STDIN_PIPE_NAME "STDIN" + +/** + * Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe + * is named "STDIN", the the contents of the pipe stream are redirected to + * STDIN of the terminal emulator for as long as the pipe is open. + */ +guac_user_pipe_handler guac_kubernetes_pipe_handler; + +#endif + diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c new file mode 100644 index 00000000..1f04b401 --- /dev/null +++ b/src/protocols/kubernetes/settings.c @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "settings.h" + +#include + +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { + "hostname", + "port", + "use-ssl", + "client-cert-file", + "client-key-file", + "ca-cert-file", + "ignore-cert", + "font-name", + "font-size", + "color-scheme", + "typescript-path", + "typescript-name", + "create-typescript-path", + "recording-path", + "recording-name", + "recording-exclude-output", + "recording-exclude-mouse", + "recording-include-keys", + "create-recording-path", + "read-only", + "backspace", + "scrollback", + NULL +}; + +enum KUBERNETES_ARGS_IDX { + + /** + * The hostname to connect to. Required. + */ + IDX_HOSTNAME, + + /** + * The port to connect to. Optional. + */ + IDX_PORT, + + /** + * Whether SSL/TLS should be used. SSL is used by default. + */ + IDX_USE_SSL, + + /** + * The filename of the certificate to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + IDX_CLIENT_CERT_FILE, + + /** + * The filename of the key to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + IDX_CLIENT_KEY_FILE, + + /** + * The filename of the certificate of the certificate authority that signed + * the certificate of the Kubernetes server. + */ + IDX_CA_CERT_FILE, + + /** + * Whether the certificate used by the Kubernetes server for SSL/TLS should + * be ignored if it cannot be validated. + */ + IDX_IGNORE_CERT, + + /** + * 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 color scheme to use, as a series of semicolon-separated color-value + * pairs: "background: ", "foreground: ", or + * "color: ", where is a number from 0 to 255, and is + * "color" or an X11 color code (e.g. "aqua" or "rgb:12/34/56"). + * The color scheme can also be one of the special values: "black-white", + * "white-black", "gray-black", or "green-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, + + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recordings which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + IDX_RECORDING_EXCLUDE_OUTPUT, + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + IDX_RECORDING_EXCLUDE_MOUSE, + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_KEYS, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + + /** + * "true" if this connection should be read-only (user input should be + * dropped), "false" or blank otherwise. + */ + IDX_READ_ONLY, + + /** + * ASCII code, as an integer to use for the backspace key, or 127 + * if not specified. + */ + IDX_BACKSPACE, + + /** + * The maximum size of the scrollback buffer in rows. + */ + IDX_SCROLLBACK, + + KUBERNETES_ARGS_COUNT +}; + +guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != KUBERNETES_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + KUBERNETES_ARGS_COUNT, argc); + return NULL; + } + + guac_kubernetes_settings* settings = + calloc(1, sizeof(guac_kubernetes_settings)); + + /* Read parameters */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + /* Parse whether SSL should be used */ + settings->use_ssl = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_USE_SSL, true); + + /* Read SSL/TLS connection details only if enabled */ + if (settings->use_ssl) { + + settings->client_cert_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CLIENT_CERT_FILE, NULL); + + settings->client_key_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CLIENT_KEY_FILE, NULL); + + settings->ca_cert_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CA_CERT_FILE, NULL); + + settings->ignore_cert = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_IGNORE_CERT, false); + + } + + /* Read-only mode */ + settings->read_only = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_READ_ONLY, false); + + /* Read maximum scrollback size */ + settings->max_scrollback = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_SCROLLBACK, GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK); + + /* Read font name */ + settings->font_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_FONT_NAME, GUAC_KUBERNETES_DEFAULT_FONT_NAME); + + /* Read font size */ + settings->font_size = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_FONT_SIZE, GUAC_KUBERNETES_DEFAULT_FONT_SIZE); + + /* Copy requested color scheme */ + settings->color_scheme = + guac_user_parse_args_string(user, GUAC_KUBERNETES_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_KUBERNETES_CLIENT_ARGS, argv, + IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT); + + /* Read typescript path */ + settings->typescript_path = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_PATH, NULL); + + /* Read typescript name */ + settings->typescript_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_NAME, GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME); + + /* Parse path creation flag */ + settings->create_typescript_path = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_CREATE_TYPESCRIPT_PATH, false); + + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_KUBERNETES_DEFAULT_RECORDING_NAME); + + /* Parse output exclusion flag */ + settings->recording_exclude_output = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_OUTPUT, false); + + /* Parse mouse exclusion flag */ + settings->recording_exclude_mouse = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_MOUSE, false); + + /* Parse key event inclusion flag */ + settings->recording_include_keys = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_KEYS, false); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + + /* Parse backspace key code */ + settings->backspace = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_BACKSPACE, 127); + + /* Parsing was successful */ + return settings; + +} + +void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { + + /* Free network connection information */ + free(settings->hostname); + free(settings->port); + + /* Free SSL/TLS details */ + free(settings->client_cert_file); + free(settings->client_key_file); + free(settings->ca_cert_file); + + /* Free display preferences */ + free(settings->font_name); + free(settings->color_scheme); + + /* Free typescript settings */ + free(settings->typescript_name); + free(settings->typescript_path); + + /* Free screen recording settings */ + free(settings->recording_name); + free(settings->recording_path); + + /* Free overall structure */ + free(settings); + +} + diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h new file mode 100644 index 00000000..3e89ce53 --- /dev/null +++ b/src/protocols/kubernetes/settings.h @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_SETTINGS_H +#define GUAC_KUBERNETES_SETTINGS_H + +#include "config.h" + +#include + +#include +#include + +/** + * The name of the font to use for the terminal if no name is specified. + */ +#define GUAC_KUBERNETES_DEFAULT_FONT_NAME "monospace" + +/** + * The size of the font to use for the terminal if no font size is specified, + * in points. + */ +#define GUAC_KUBERNETES_DEFAULT_FONT_SIZE 12 + +/** + * The port to connect to when initiating any Kubernetes connection, if no + * other port is specified. + */ +#define GUAC_KUBERNETES_DEFAULT_PORT "8443" + +/** + * The filename to use for the typescript, if not specified. + */ +#define GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME "typescript" + +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording" + +/** + * The default maximum scrollback size in rows. + */ +#define GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK 1000 + +/** + * Settings for the Kubernetes connection. The values for this structure are + * parsed from the arguments given during the Guacamole protocol handshake + * using the guac_kubernetes_parse_args() function. + */ +typedef struct guac_kubernetes_settings { + + /** + * The hostname of the Kubernetes server to connect to. + */ + char* hostname; + + /** + * The port of the Kubernetes server to connect to. + */ + char* port; + + /** + * Whether SSL/TLS should be used. + */ + bool use_ssl; + + /** + * The filename of the certificate to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + char* client_cert_file; + + /** + * The filename of the key to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + char* client_key_file; + + /** + * The filename of the certificate of the certificate authority that signed + * the certificate of the Kubernetes server. + */ + char* ca_cert_file; + + /** + * Whether the certificate used by the Kubernetes server for SSL/TLS should + * be ignored if it cannot be validated. + */ + bool ignore_cert; + + /** + * Whether this connection is read-only, and user input should be dropped. + */ + bool read_only; + + /** + * The maximum size of the scrollback buffer in rows. + */ + int max_scrollback; + + /** + * 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; + + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + bool recording_exclude_output; + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + bool recording_exclude_mouse; + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_keys; + + /** + * The ASCII code, as an integer, that the Kubernetes client will use when + * the backspace key is pressed. By default, this is 127, ASCII delete, if + * not specified in the client settings. + */ + int backspace; + +} guac_kubernetes_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_kubernetes_settings_free() when no longer needed. If the arguments + * fail to parse, NULL is returned. + */ +guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_kubernetes_settings object, having been previously + * allocated via guac_kubernetes_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_kubernetes_settings_free(guac_kubernetes_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_KUBERNETES_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c new file mode 100644 index 00000000..62666cb5 --- /dev/null +++ b/src/protocols/kubernetes/user.c @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "clipboard.h" +#include "input.h" +#include "kubernetes.h" +#include "pipe.h" +#include "settings.h" +#include "terminal/terminal.h" +#include "user.h" + +#include +#include +#include + +#include +#include + +int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Parse provided arguments */ + guac_kubernetes_settings* settings = guac_kubernetes_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; + } + + /* Store settings at user level */ + user->data = settings; + + /* Connect to Kubernetes if owner */ + if (user->owner) { + + /* Store owner's settings at client level */ + kubernetes_client->settings = settings; + + /* Start client thread */ + if (pthread_create(&(kubernetes_client->client_thread), NULL, + guac_kubernetes_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to start Kubernetes client thread"); + return 1; + } + + } + + /* If not owner, synchronize with current display */ + else { + guac_terminal_dup(kubernetes_client->term, user, user->socket); + guac_socket_flush(user->socket); + } + + /* Only handle events if not read-only */ + if (!settings->read_only) { + + /* General mouse/keyboard/clipboard events */ + user->key_handler = guac_kubernetes_user_key_handler; + user->mouse_handler = guac_kubernetes_user_mouse_handler; + user->clipboard_handler = guac_kubernetes_clipboard_handler; + + /* STDIN redirection */ + user->pipe_handler = guac_kubernetes_pipe_handler; + + /* Display size change events */ + user->size_handler = guac_kubernetes_user_size_handler; + + } + + return 0; + +} + +int guac_kubernetes_user_leave_handler(guac_user* user) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) user->client->data; + + /* Update shared cursor state */ + guac_common_cursor_remove_user(kubernetes_client->term->cursor, user); + + /* Free settings if not owner (owner settings will be freed with client) */ + if (!user->owner) { + guac_kubernetes_settings* settings = + (guac_kubernetes_settings*) user->data; + guac_kubernetes_settings_free(settings); + } + + return 0; +} + diff --git a/src/protocols/kubernetes/user.h b/src/protocols/kubernetes/user.h new file mode 100644 index 00000000..d235b2b7 --- /dev/null +++ b/src/protocols/kubernetes/user.h @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_USER_H +#define GUAC_KUBERNETES_USER_H + +#include "config.h" + +#include + +/** + * Handler for joining users. + */ +guac_user_join_handler guac_kubernetes_user_join_handler; + +/** + * Handler for leaving users. + */ +guac_user_leave_handler guac_kubernetes_user_leave_handler; + +#endif + From 5bae422b29939c2e08aff13b33bdb8c2b9ab2ed9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 9 Sep 2018 21:49:58 -0700 Subject: [PATCH 015/175] GUACAMOLE-623: libwebsockets requires an integer port number. --- src/protocols/kubernetes/settings.c | 3 +-- src/protocols/kubernetes/settings.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 1f04b401..5ee66716 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -278,7 +278,7 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, /* Read port */ settings->port = - guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT); /* Read typescript path */ @@ -340,7 +340,6 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { /* Free network connection information */ free(settings->hostname); - free(settings->port); /* Free SSL/TLS details */ free(settings->client_cert_file); diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index 3e89ce53..fea6cf1a 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -42,7 +42,7 @@ * The port to connect to when initiating any Kubernetes connection, if no * other port is specified. */ -#define GUAC_KUBERNETES_DEFAULT_PORT "8443" +#define GUAC_KUBERNETES_DEFAULT_PORT 8443 /** * The filename to use for the typescript, if not specified. @@ -74,7 +74,7 @@ typedef struct guac_kubernetes_settings { /** * The port of the Kubernetes server to connect to. */ - char* port; + int port; /** * Whether SSL/TLS should be used. From 519c90a88760ae4db4ad4db8b0f4f02dcef599b4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 9 Sep 2018 22:54:50 -0700 Subject: [PATCH 016/175] GUACAMOLE-623: Default to unencrypted Kubernetes connections. --- src/protocols/kubernetes/settings.c | 4 ++-- src/protocols/kubernetes/settings.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 5ee66716..50daa6f9 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -68,7 +68,7 @@ enum KUBERNETES_ARGS_IDX { IDX_PORT, /** - * Whether SSL/TLS should be used. SSL is used by default. + * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used. */ IDX_USE_SSL, @@ -223,7 +223,7 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, /* Parse whether SSL should be used */ settings->use_ssl = guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, - IDX_USE_SSL, true); + IDX_USE_SSL, false); /* Read SSL/TLS connection details only if enabled */ if (settings->use_ssl) { diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index fea6cf1a..8f42f8ca 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -42,7 +42,7 @@ * The port to connect to when initiating any Kubernetes connection, if no * other port is specified. */ -#define GUAC_KUBERNETES_DEFAULT_PORT 8443 +#define GUAC_KUBERNETES_DEFAULT_PORT 8080 /** * The filename to use for the typescript, if not specified. From 7165fa949d21386d19cbda795fbc849e916461d0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 9 Sep 2018 22:55:38 -0700 Subject: [PATCH 017/175] GUACAMOLE-623: Stub out implementation of WebSocket client for Kubernetes. --- src/protocols/kubernetes/kubernetes.c | 177 +++++++++++++++++++++++++- src/protocols/kubernetes/kubernetes.h | 6 + 2 files changed, 178 insertions(+), 5 deletions(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 231d78d3..98e49ecf 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -36,6 +37,104 @@ #include #include +/** + * The name of the WebSocket protocol specific to Kubernetes which should be + * sent to the Kubernetes server when attaching to a pod. + */ +#define GUAC_KUBERNETES_LWS_PROTOCOL "v4.channel.k8s.io" + +/** + * Callback invoked by libwebsockets for events related to a WebSocket being + * used for communicating with an attached Kubernetes pod. + * + * @param wsi + * The libwebsockets handle for the WebSocket connection. + * + * @param reason + * The reason (event) that this callback was invoked. + * + * @param user + * Arbitrary data assocated with the WebSocket session. This will always + * be a pointer to the guac_client instance. + * + * @param in + * A pointer to arbitrary, reason-specific data. + * + * @param length + * An arbitrary, reason-specific length value. + * + * @return + * An undocumented integer value related the success of handling the + * event, or -1 if the WebSocket connection should be closed. + */ +static int guac_kubernetes_lws_callback(struct lws* wsi, + enum lws_callback_reasons reason, void* user, + void* in, size_t length) { + + guac_client* client = (guac_client*) user; + /*guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data;*/ + + /* Request connection closure if client is stopped */ + if (client->state != GUAC_CLIENT_RUNNING) + return -1; + + switch (reason) { + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Error connecting to Kubernetes server: %s", + in != NULL ? (char*) in : "(no error description " + "available)"); + break; + + /* Logged in */ + case LWS_CALLBACK_CLIENT_ESTABLISHED: + guac_client_log(client, GUAC_LOG_INFO, + "Kubernetes connection successful."); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + guac_client_log(client, GUAC_LOG_DEBUG, "Received: %s", + (const char*) in); + break; + + /* TODO: Only send data here. Request callback for writing via lws_callback_on_writable(some struct lws*) */ + case LWS_CALLBACK_CLIENT_WRITEABLE: + break; + + case LWS_CALLBACK_CLOSED: + /* TODO: case LWS_CALLBACK_CLIENT_CLOSED: <-- Needs test and #ifdef */ + guac_client_stop(client); + guac_client_log(client, GUAC_LOG_DEBUG, "WebSocket connection to " + "Kubernetes server closed."); + break; + + /* No other event types are applicable */ + default: + guac_client_log(client, GUAC_LOG_DEBUG, "Unexpected libwebsockets " + "reason: %i", reason); + break; + + } + + return lws_callback_http_dummy(wsi, reason, user, in, length); + +} + +/** + * List of all WebSocket protocols which should be declared as supported by + * libwebsockets during the initial WebSocket handshake, along with + * corresponding event-handling callbacks. + */ +struct lws_protocols guac_kubernetes_lws_protocols[] = { + { + .name = GUAC_KUBERNETES_LWS_PROTOCOL, + .callback = guac_kubernetes_lws_callback + }, + { 0 } +}; + /** * Input thread, started by the main Kubernetes client thread. This thread * continuously reads from the terminal's STDIN and transfers all read @@ -111,11 +210,69 @@ void* guac_kubernetes_client_thread(void* data) { settings->create_typescript_path); } - /* TODO: Open WebSocket connection to Kubernetes */ + /* Init libwebsockets context creation parameters */ + struct lws_context_creation_info context_info = { + .port = CONTEXT_PORT_NO_LISTEN, /* We are not a WebSocket server */ + .protocols = guac_kubernetes_lws_protocols + }; - /* Logged in */ - guac_client_log(client, GUAC_LOG_INFO, - "Kubernetes connection successful."); + /* Init WebSocket connection parameters which do not vary by Guacmaole + * connection parameters or creation of future libwebsockets objects */ + struct lws_client_connect_info connection_info = { + .host = settings->hostname, + .address = settings->hostname, + .origin = settings->hostname, + .port = settings->port, + .protocol = GUAC_KUBERNETES_LWS_PROTOCOL, + .pwsi = &kubernetes_client->wsi, + .userdata = client + }; + + /* If requested, use an SSL/TLS connection for communication with + * Kubernetes */ + if (settings->use_ssl) { + + /* Enable use of SSL/TLS */ + context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + connection_info.ssl_connection = LCCSCF_USE_SSL; + + /* Bypass certificate checks if requested */ + if (settings->ignore_cert) { + connection_info.ssl_connection |= + LCCSCF_ALLOW_SELFSIGNED + | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK + | LCCSCF_ALLOW_EXPIRED; + } + + /* Otherwise use the given CA certificate to validate (if any) */ + else + context_info.client_ssl_ca_filepath = settings->ca_cert_file; + + /* Certificate and key file for SSL/TLS client auth */ + context_info.client_ssl_cert_filepath = settings->client_cert_file; + context_info.client_ssl_private_key_filepath = settings->client_key_file; + + } + + /* Create libwebsockets context */ + struct lws_context* context = lws_create_context(&context_info); + if (!context) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Initialization of libwebsockets failed"); + return NULL; + } + + /* FIXME: Generate path dynamically */ + connection_info.context = context; + connection_info.path = "/api/v1/namespaces/default/pods/my-shell-68974bb7f7-rpjgr/attach?container=my-shell&stdin=true&stdout=true&tty=true"; + + /* Open WebSocket connection to Kubernetes */ + kubernetes_client->wsi = lws_client_connect_via_info(&connection_info); + if (kubernetes_client->wsi == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Connection via libwebsockets failed"); + return NULL; + } /* Start input thread */ if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) { @@ -123,12 +280,22 @@ void* guac_kubernetes_client_thread(void* data) { return NULL; } - /* TODO: While data available, write to terminal */ + /* As long as client is connected, continue polling libwebsockets */ + while (client->state == GUAC_CLIENT_RUNNING) { + + /* Cease polling libwebsockets if an error condition is signalled */ + if (lws_service(context, 1000) < 0) + break; + + } /* Kill client and Wait for input thread to die */ guac_client_stop(client); pthread_join(input_thread, NULL); + /* All done with libwebsockets */ + lws_context_destroy(context); + guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended."); return NULL; diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index f8035ae5..89c172f7 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -26,6 +26,7 @@ #include "settings.h" #include "terminal/terminal.h" +#include #include /** @@ -38,6 +39,11 @@ typedef struct guac_kubernetes_client { */ guac_kubernetes_settings* settings; + /** + * The connected WebSocket. + */ + struct lws* wsi; + /** * The Kubernetes client thread. */ From f72877bf0d7c68fc2c08eb442d749ce096dc8118 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 9 Sep 2018 23:50:30 -0700 Subject: [PATCH 018/175] GUACAMOLE-623: Handle data received from Kubernetes. --- src/protocols/kubernetes/kubernetes.c | 70 +++++++++++++++++++++------ src/protocols/kubernetes/kubernetes.h | 26 ++++++++++ 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 98e49ecf..ae2a8947 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -38,10 +38,44 @@ #include /** - * The name of the WebSocket protocol specific to Kubernetes which should be - * sent to the Kubernetes server when attaching to a pod. + * Handles data received from Kubernetes over WebSocket, decoding the channel + * index of the received data and forwarding that data accordingly. + * + * @param client + * The guac_client associated with the connection to Kubernetes. + * + * @param buffer + * The data received from Kubernetes. + * + * @param length + * The size of the data received from Kubernetes, in bytes. */ -#define GUAC_KUBERNETES_LWS_PROTOCOL "v4.channel.k8s.io" +static void guac_kubernetes_receive_data(guac_client* client, + const char* buffer, size_t length) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Strip channel index from beginning of buffer */ + int channel = *(buffer++); + length--; + + switch (channel) { + + /* Write STDOUT / STDERR directly to terminal as output */ + case GUAC_KUBERNETES_CHANNEL_STDOUT: + case GUAC_KUBERNETES_CHANNEL_STDERR: + guac_terminal_write(kubernetes_client->term, buffer, length); + break; + + /* Ignore data on other channels */ + default: + guac_client_log(client, GUAC_LOG_DEBUG, "Received %i bytes along " + "channel %i.", length, channel); + + } + +} /** * Callback invoked by libwebsockets for events related to a WebSocket being @@ -71,16 +105,15 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t length) { + /* Request connection closure if client is stopped (note that the user + * pointer passed by libwebsockets may be NULL for some events) */ guac_client* client = (guac_client*) user; - /*guac_kubernetes_client* kubernetes_client = - (guac_kubernetes_client*) client->data;*/ - - /* Request connection closure if client is stopped */ - if (client->state != GUAC_CLIENT_RUNNING) + if (client != NULL && client->state != GUAC_CLIENT_RUNNING) return -1; switch (reason) { + /* Failed to connect */ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, "Error connecting to Kubernetes server: %s", @@ -88,23 +121,29 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, "available)"); break; - /* Logged in */ + /* Connected / logged in */ case LWS_CALLBACK_CLIENT_ESTABLISHED: guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection successful."); break; + /* Data received via WebSocket */ case LWS_CALLBACK_CLIENT_RECEIVE: - guac_client_log(client, GUAC_LOG_DEBUG, "Received: %s", - (const char*) in); + guac_kubernetes_receive_data(client, (const char*) in, length); break; /* TODO: Only send data here. Request callback for writing via lws_callback_on_writable(some struct lws*) */ case LWS_CALLBACK_CLIENT_WRITEABLE: break; + /* TODO: Add configure test */ +#ifdef HAVE_LWS_CALLBACK_CLIENT_CLOSED + /* Connection closed (client-specific) */ + case LWS_CALLBACK_CLIENT_CLOSED: +#endif + + /* Connection closed */ case LWS_CALLBACK_CLOSED: - /* TODO: case LWS_CALLBACK_CLIENT_CLOSED: <-- Needs test and #ifdef */ guac_client_stop(client); guac_client_log(client, GUAC_LOG_DEBUG, "WebSocket connection to " "Kubernetes server closed."); @@ -112,8 +151,6 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, /* No other event types are applicable */ default: - guac_client_log(client, GUAC_LOG_DEBUG, "Unexpected libwebsockets " - "reason: %i", reason); break; } @@ -213,7 +250,10 @@ void* guac_kubernetes_client_thread(void* data) { /* Init libwebsockets context creation parameters */ struct lws_context_creation_info context_info = { .port = CONTEXT_PORT_NO_LISTEN, /* We are not a WebSocket server */ - .protocols = guac_kubernetes_lws_protocols + .uid = -1, + .gid = -1, + .protocols = guac_kubernetes_lws_protocols, + .user = client }; /* Init WebSocket connection parameters which do not vary by Guacmaole diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index 89c172f7..89b6678f 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -29,6 +29,32 @@ #include #include +/** + * The name of the WebSocket protocol specific to Kubernetes which should be + * sent to the Kubernetes server when attaching to a pod. + */ +#define GUAC_KUBERNETES_LWS_PROTOCOL "v4.channel.k8s.io" + +/** + * The index of the Kubernetes channel used for STDIN. + */ +#define GUAC_KUBERNETES_CHANNEL_STDIN 0 + +/** + * The index of the Kubernetes channel used for STDOUT. + */ +#define GUAC_KUBERNETES_CHANNEL_STDOUT 1 + +/** + * The index of the Kubernetes channel used for STDERR. + */ +#define GUAC_KUBERNETES_CHANNEL_STDERR 2 + +/** + * The index of the Kubernetes channel used for terminal resize messages. + */ +#define GUAC_KUBERNETES_CHANNEL_RESIZE 4 + /** * Kubernetes-specific client data. */ From cbe593503f4cf819af32abc0b661662aa3fadc9e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 00:25:49 -0700 Subject: [PATCH 019/175] GUACAMOLE-623: Do not return -1 from libwebsockets callback. Doing so results in automatic cleanup of part of the context, resulting in a segfault when lws_context_destroy() is invoked. --- src/protocols/kubernetes/kubernetes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index ae2a8947..e38d5e37 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -109,7 +109,7 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, * pointer passed by libwebsockets may be NULL for some events) */ guac_client* client = (guac_client*) user; if (client != NULL && client->state != GUAC_CLIENT_RUNNING) - return -1; + return lws_callback_http_dummy(wsi, reason, user, in, length); switch (reason) { From f35517b3ff42f268e20f84282bc93130fa86e98d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 01:26:13 -0700 Subject: [PATCH 020/175] GUACAMOLE-623: Add outbound message buffer. --- src/protocols/kubernetes/client.c | 13 +----- src/protocols/kubernetes/kubernetes.c | 31 ++++++++++---- src/protocols/kubernetes/kubernetes.h | 58 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 1b5d175d..77aa6473 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -67,17 +67,8 @@ int guac_kubernetes_client_free_handler(guac_client* client) { guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; - /* Clean up recording, if in progress */ - if (kubernetes_client->recording != NULL) - guac_common_recording_free(kubernetes_client->recording); - - /* Kill terminal */ - guac_terminal_free(kubernetes_client->term); - - /* TODO: Wait for and free WebSocket session, if connected */ - /*if (kubernetes_client->websocket != NULL) { - pthread_join(kubernetes_client->client_thread, NULL); - }*/ + /* Wait client thread to terminate */ + pthread_join(kubernetes_client->client_thread, NULL); /* Free settings */ if (kubernetes_client->settings != NULL) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index e38d5e37..62fb6ee7 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -206,6 +206,8 @@ static void* guac_kubernetes_input_thread(void* data) { void* guac_kubernetes_client_thread(void* data) { + struct lws_context* context = NULL; + guac_client* client = (guac_client*) data; guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; @@ -236,7 +238,7 @@ void* guac_kubernetes_client_thread(void* data) { if (kubernetes_client->term == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Terminal initialization failed"); - return NULL; + goto fail; } /* Set up typescript, if requested */ @@ -295,11 +297,11 @@ void* guac_kubernetes_client_thread(void* data) { } /* Create libwebsockets context */ - struct lws_context* context = lws_create_context(&context_info); + context = lws_create_context(&context_info); if (!context) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Initialization of libwebsockets failed"); - return NULL; + goto fail; } /* FIXME: Generate path dynamically */ @@ -311,13 +313,16 @@ void* guac_kubernetes_client_thread(void* data) { if (kubernetes_client->wsi == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Connection via libwebsockets failed"); - return NULL; + goto fail; } + /* Init outbound message buffer */ + pthread_mutex_init(&(kubernetes_client->outbound_message_lock), NULL); + /* Start input thread */ if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); - return NULL; + goto fail; } /* As long as client is connected, continue polling libwebsockets */ @@ -330,11 +335,23 @@ void* guac_kubernetes_client_thread(void* data) { } /* Kill client and Wait for input thread to die */ + guac_terminal_stop(kubernetes_client->term); guac_client_stop(client); pthread_join(input_thread, NULL); - /* All done with libwebsockets */ - lws_context_destroy(context); +fail: + + /* Kill and free terminal, if allocated */ + if (kubernetes_client->term != NULL) + guac_terminal_free(kubernetes_client->term); + + /* Clean up recording, if in progress */ + if (kubernetes_client->recording != NULL) + guac_common_recording_free(kubernetes_client->recording); + + /* Free WebSocket context if successfully allocated */ + if (context != NULL) + lws_context_destroy(context); guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended."); return NULL; diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index 89b6678f..8fb917db 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -55,6 +55,37 @@ */ #define GUAC_KUBERNETES_CHANNEL_RESIZE 4 +/** + * The maximum number of messages to allow within the outbound message buffer. + * If messages are sent despite the buffer being full, those messages will be + * dropped. + */ +#define GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES 8 + +/** + * An outbound message to be received by Kubernetes over WebSocket. + */ +typedef struct guac_kubernetes_message { + + /** + * The index of the channel receiving the data, such as + * GUAC_KUBERNETES_CHANNEL_STDIN. + */ + uint8_t channel; + + /** + * The data that should be sent to Kubernetes (along with the channel + * index). + */ + char data[1024]; + + /** + * The length of the data to be sent, excluding the channel index. + */ + int length; + +} guac_kubernetes_message; + /** * Kubernetes-specific client data. */ @@ -70,6 +101,33 @@ typedef struct guac_kubernetes_client { */ struct lws* wsi; + /** + * Outbound message ring buffer for outbound WebSocket messages. As + * libwebsockets uses an event loop for all operations, outbound messages + * may be sent only in context of a particular event received via a + * callback. Until that event is received, pending data must accumulate in + * a buffer. + */ + guac_kubernetes_message outbound_messages[GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES]; + + /** + * The number of messages currently waiting in the outbound message + * buffer. + */ + int outbound_messages_waiting; + + /** + * The index of the oldest entry in the outbound message buffer. Newer + * messages follow this entry. + */ + int outbound_messages_top; + + /** + * Lock which is acquired when the outbound message buffer is being read + * or manipulated. + */ + pthread_mutex_t outbound_message_lock; + /** * The Kubernetes client thread. */ From b7c938c239f0c61b6122c817c6ce44309d61bd1f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 02:16:36 -0700 Subject: [PATCH 021/175] GUACAMOLE-623: Send typed data to Kubernetes via the STDIN channel. --- src/protocols/kubernetes/kubernetes.c | 151 ++++++++++++++++++++++++-- src/protocols/kubernetes/kubernetes.h | 26 ++++- 2 files changed, 164 insertions(+), 13 deletions(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 62fb6ee7..aadb448d 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -77,6 +77,127 @@ static void guac_kubernetes_receive_data(guac_client* client, } +/** + * Requests that the given data be sent along the given channel to the + * Kubernetes server when the WebSocket connection is next available for + * writing. If the WebSocket connection has not been available for writing for + * long enough that the outbound message buffer is full, the request to send + * this particular message will be dropped. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @param channel + * The Kubernetes channel on which to send the message, + * such as GUAC_KUBERNETES_CHANNEL_STDIN. + * + * @param data + * A buffer containing the data to send. + * + * @param length + * The number of bytes to send. + */ +static void guac_kubernetes_send_message(guac_client* client, + int channel, const char* data, int length) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); + + /* Add message to buffer if space is available */ + if (kubernetes_client->outbound_messages_waiting + < GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES) { + + /* Calculate storage position of next message */ + int index = (kubernetes_client->outbound_messages_top + + kubernetes_client->outbound_messages_waiting) + % GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; + + /* Obtain pointer to message slot at calculated position */ + guac_kubernetes_message* message = + &(kubernetes_client->outbound_messages[index]); + + /* Copy details of message into buffer */ + message->channel = channel; + memcpy(message->data, data, length); + message->length = length; + + /* One more message is now waiting */ + kubernetes_client->outbound_messages_waiting++; + + /* Notify libwebsockets that we need a callback to send pending + * messages */ + lws_callback_on_writable(kubernetes_client->wsi); + lws_cancel_service(kubernetes_client->context); + + } + + /* Warn if data has to be dropped */ + else + guac_client_log(client, GUAC_LOG_WARNING, "Send buffer could not be " + "flushed in time to handle additional data. Outbound " + "message dropped."); + + pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); + +} + +/** + * Writes the oldest pending message within the outbound message queue, + * as scheduled with guac_kubernetes_send_message(), removing that message + * from the queue. This function MAY NOT be invoked outside the libwebsockets + * event callback and MUST only be invoked in the context of a + * LWS_CALLBACK_CLIENT_WRITEABLE event. If no messages are pending, this + * function has no effect. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @return + * true if messages still remain to be written within the outbound message + * queue, false otherwise. + */ +static bool guac_kubernetes_write_pending_message(guac_client* client) { + + bool messages_remain; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); + + /* Send one message from top of buffer */ + if (kubernetes_client->outbound_messages_waiting > 0) { + + /* Obtain pointer to message at top */ + int top = kubernetes_client->outbound_messages_top; + guac_kubernetes_message* message = + &(kubernetes_client->outbound_messages[top]); + + /* Write message including channel index */ + lws_write(kubernetes_client->wsi, + ((unsigned char*) message) + LWS_PRE, + message->length + 1, LWS_WRITE_BINARY); + + /* Advance top to next message */ + kubernetes_client->outbound_messages_top++; + kubernetes_client->outbound_messages_top %= + GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; + + /* One less message is waiting */ + kubernetes_client->outbound_messages_waiting--; + + } + + /* Record whether messages remained at time of completion */ + messages_remain = (kubernetes_client->outbound_messages_waiting > 0); + + pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); + + return messages_remain; + +} + /** * Callback invoked by libwebsockets for events related to a WebSocket being * used for communicating with an attached Kubernetes pod. @@ -132,8 +253,14 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, guac_kubernetes_receive_data(client, (const char*) in, length); break; - /* TODO: Only send data here. Request callback for writing via lws_callback_on_writable(some struct lws*) */ + /* WebSocket is ready for writing */ case LWS_CALLBACK_CLIENT_WRITEABLE: + + /* Send any pending messages, requesting another callback if + * yet more messages remain */ + if (guac_kubernetes_write_pending_message(client)) + lws_callback_on_writable(wsi); + break; /* TODO: Add configure test */ @@ -189,14 +316,15 @@ static void* guac_kubernetes_input_thread(void* data) { guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; - char buffer[8192]; + char buffer[GUAC_KUBERNETES_MAX_MESSAGE_SIZE]; int bytes_read; /* Write all data read */ while ((bytes_read = guac_terminal_read_stdin(kubernetes_client->term, buffer, sizeof(buffer))) > 0) { - /* TODO: Send to Kubernetes */ - guac_terminal_write(kubernetes_client->term, buffer, bytes_read); + /* Send received data to Kubernetes along STDIN channel */ + guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_STDIN, + buffer, bytes_read); } @@ -206,8 +334,6 @@ static void* guac_kubernetes_input_thread(void* data) { void* guac_kubernetes_client_thread(void* data) { - struct lws_context* context = NULL; - guac_client* client = (guac_client*) data; guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; @@ -297,15 +423,15 @@ void* guac_kubernetes_client_thread(void* data) { } /* Create libwebsockets context */ - context = lws_create_context(&context_info); - if (!context) { + kubernetes_client->context = lws_create_context(&context_info); + if (!kubernetes_client->context) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Initialization of libwebsockets failed"); goto fail; } /* FIXME: Generate path dynamically */ - connection_info.context = context; + connection_info.context = kubernetes_client->context; connection_info.path = "/api/v1/namespaces/default/pods/my-shell-68974bb7f7-rpjgr/attach?container=my-shell&stdin=true&stdout=true&tty=true"; /* Open WebSocket connection to Kubernetes */ @@ -329,7 +455,8 @@ void* guac_kubernetes_client_thread(void* data) { while (client->state == GUAC_CLIENT_RUNNING) { /* Cease polling libwebsockets if an error condition is signalled */ - if (lws_service(context, 1000) < 0) + if (lws_service(kubernetes_client->context, + GUAC_KUBERNETES_SERVICE_INTERVAL) < 0) break; } @@ -350,8 +477,8 @@ fail: guac_common_recording_free(kubernetes_client->recording); /* Free WebSocket context if successfully allocated */ - if (context != NULL) - lws_context_destroy(context); + if (kubernetes_client->context != NULL) + lws_context_destroy(kubernetes_client->context); guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended."); return NULL; diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index 8fb917db..761a897c 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -55,6 +55,13 @@ */ #define GUAC_KUBERNETES_CHANNEL_RESIZE 4 +/** + * The maximum amount of data to include in any particular WebSocket message + * to Kubernetes. This excludes the storage space required for the channel + * index. + */ +#define GUAC_KUBERNETES_MAX_MESSAGE_SIZE 1024 + /** * The maximum number of messages to allow within the outbound message buffer. * If messages are sent despite the buffer being full, those messages will be @@ -62,11 +69,23 @@ */ #define GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES 8 +/** + * The maximum number of milliseconds to wait for a libwebsockets event to + * occur before entering another iteration of the libwebsockets event loop. + */ +#define GUAC_KUBERNETES_SERVICE_INTERVAL 1000 + /** * An outbound message to be received by Kubernetes over WebSocket. */ typedef struct guac_kubernetes_message { + /** + * lws_write() requires leading padding of LWS_PRE bytes to provide + * scratch space for WebSocket framing. + */ + uint8_t _padding[LWS_PRE]; + /** * The index of the channel receiving the data, such as * GUAC_KUBERNETES_CHANNEL_STDIN. @@ -77,7 +96,7 @@ typedef struct guac_kubernetes_message { * The data that should be sent to Kubernetes (along with the channel * index). */ - char data[1024]; + char data[GUAC_KUBERNETES_MAX_MESSAGE_SIZE]; /** * The length of the data to be sent, excluding the channel index. @@ -96,6 +115,11 @@ typedef struct guac_kubernetes_client { */ guac_kubernetes_settings* settings; + /** + * The libwebsockets context associated with the connected WebSocket. + */ + struct lws_context* context; + /** * The connected WebSocket. */ From fe7edce5694a718ce233b4d4c85b386be4240262 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 02:50:15 -0700 Subject: [PATCH 022/175] GUACAMOLE-623: Add support for terminal resize. Redraw Kubernetes container upon connect. --- src/protocols/kubernetes/input.c | 4 +- src/protocols/kubernetes/kubernetes.c | 55 ++++++++++++++++++++++++++- src/protocols/kubernetes/kubernetes.h | 38 ++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/protocols/kubernetes/input.c b/src/protocols/kubernetes/input.c index 9bf5b717..e73772e5 100644 --- a/src/protocols/kubernetes/input.c +++ b/src/protocols/kubernetes/input.c @@ -88,7 +88,9 @@ int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) { /* Resize terminal */ guac_terminal_resize(terminal, width, height); - /* TODO: Update Kubernetes terminal window size if connected */ + /* Update Kubernetes terminal window size if connected */ + guac_kubernetes_resize(client, terminal->term_height, + terminal->term_width); return 0; } diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index aadb448d..53a8580d 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -246,6 +246,11 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, case LWS_CALLBACK_CLIENT_ESTABLISHED: guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection successful."); + + /* Schedule check for pending messages in case messages were added + * to the outbound message buffer prior to the connection being + * fully established */ + lws_callback_on_writable(wsi); break; /* Data received via WebSocket */ @@ -260,7 +265,6 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, * yet more messages remain */ if (guac_kubernetes_write_pending_message(client)) lws_callback_on_writable(wsi); - break; /* TODO: Add configure test */ @@ -451,6 +455,11 @@ void* guac_kubernetes_client_thread(void* data) { goto fail; } + /* Force a redraw of the attached display (there will be no content + * otherwise, given the stream nature of attaching to a running + * container) */ + guac_kubernetes_force_redraw(client); + /* As long as client is connected, continue polling libwebsockets */ while (client->state == GUAC_CLIENT_RUNNING) { @@ -485,3 +494,47 @@ fail: } +void guac_kubernetes_resize(guac_client* client, int rows, int columns) { + + char buffer[64]; + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Send request only if different from last request */ + if (kubernetes_client->rows != rows || + kubernetes_client->columns != columns) { + + kubernetes_client->rows = rows; + kubernetes_client->columns = columns; + + /* Construct terminal resize message for Kubernetes */ + int length = snprintf(buffer, sizeof(buffer), + "{\"Width\":%i,\"Height\":%i}", columns, rows); + + /* Schedule message for sending */ + guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_RESIZE, + buffer, length); + + } + +} + +void guac_kubernetes_force_redraw(guac_client* client) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Get current terminal dimensions */ + guac_terminal* term = kubernetes_client->term; + int rows = term->term_height; + int columns = term->term_width; + + /* Force a redraw by increasing the terminal size by one character in + * each dimension and then resizing it back to normal (the same technique + * used by kubectl */ + guac_kubernetes_resize(client, rows + 1, columns + 1); + guac_kubernetes_resize(client, rows, columns); + +} + diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index 761a897c..fedf77a2 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -167,6 +167,18 @@ typedef struct guac_kubernetes_client { */ guac_terminal* term; + /** + * The number of rows last sent to Kubernetes in a terminal resize + * request. + */ + int rows; + + /** + * The number of columns last sent to Kubernetes in a terminal resize + * request. + */ + int columns; + /** * The in-progress session recording, or NULL if no recording is in * progress. @@ -181,5 +193,31 @@ typedef struct guac_kubernetes_client { */ void* guac_kubernetes_client_thread(void* data); +/** + * Sends a message to the Kubernetes server requesting that the terminal be + * resized to the given dimensions. This message may be queued until the + * underlying WebSocket connection is ready to send. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @param rows + * The new terminal size in rows. + * + * @param columns + * The new terminal size in columns. + */ +void guac_kubernetes_resize(guac_client* client, int rows, int columns); + +/** + * Sends messages to the Kubernetes server such that the terminal is forced + * to redraw. This function should be invoked at the beginning of each + * session in order to restore expected display state. + * + * @param client + * The guac_client associated with the Kubernetes connection. + */ +void guac_kubernetes_force_redraw(guac_client* client); + #endif From 34f8f8b30d84e7622005f2ba0ab704758cc88767 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 15:01:48 -0700 Subject: [PATCH 023/175] GUACAMOLE-623: Redirect libwebsockets logging to guacd's debug level log. --- src/protocols/kubernetes/client.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 77aa6473..450e1e87 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -33,8 +33,39 @@ #include +/** + * Static reference to the guac_client associated with the active Kubernetes + * connection. As guacd guarantees that each main client connection is + * isolated within its own process, this is safe. + */ +static guac_client* guac_kubernetes_lws_log_client = NULL; + +/** + * Logging callback invoked by libwebsockets to log a single line of logging + * output. As libwebsockets messages are all generally low-level, the log + * level provided by libwebsockets is ignored here, with all messages logged + * instead at guacd's debug level. + * + * @param level + * The libwebsockets log level associated with the log message. This value + * is ignored by this implementation of the logging callback. + * + * @param line + * The line of logging output to log. + */ +static void guac_kubernetes_log(int level, const char* line) { + if (guac_kubernetes_lws_log_client != NULL) + guac_client_log(guac_kubernetes_lws_log_client, GUAC_LOG_DEBUG, + "libwebsockets: %s", line); +} + int guac_client_init(guac_client* client) { + /* Redirect libwebsockets logging */ + guac_kubernetes_lws_log_client = client; + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, + guac_kubernetes_log); + /* Set client args */ client->args = GUAC_KUBERNETES_CLIENT_ARGS; From ed560938886e92dbe656d610966585b8178c2d73 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 18:39:06 -0700 Subject: [PATCH 024/175] GUACAMOLE-623: Generate Kubernetes API endpoint dynamically. --- src/protocols/kubernetes/Makefile.am | 2 + src/protocols/kubernetes/kubernetes.c | 27 +++++- src/protocols/kubernetes/settings.c | 53 +++++++++-- src/protocols/kubernetes/settings.h | 24 +++++ src/protocols/kubernetes/url.c | 126 ++++++++++++++++++++++++++ src/protocols/kubernetes/url.h | 87 ++++++++++++++++++ 6 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 src/protocols/kubernetes/url.c create mode 100644 src/protocols/kubernetes/url.h diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index d864967f..9e50feb6 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -29,6 +29,7 @@ libguac_client_kubernetes_la_SOURCES = \ pipe.c \ settings.c \ kubernetes.c \ + url.c \ user.c noinst_HEADERS = \ @@ -38,6 +39,7 @@ noinst_HEADERS = \ pipe.h \ settings.h \ kubernetes.h \ + url.h \ user.h libguac_client_kubernetes_la_CFLAGS = \ diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 53a8580d..380d1d3b 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -21,6 +21,7 @@ #include "common/recording.h" #include "kubernetes.h" #include "terminal/terminal.h" +#include "url.h" #include #include @@ -345,6 +346,28 @@ void* guac_kubernetes_client_thread(void* data) { guac_kubernetes_settings* settings = kubernetes_client->settings; pthread_t input_thread; + char endpoint_path[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; + + /* Verify that the pod name was specified (it's always required) */ + if (settings->kubernetes_pod == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "The name of the Kubernetes pod is a required parameter."); + goto fail; + } + + /* Generate endpoint for attachment URL */ + if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path), + settings->kubernetes_namespace, + settings->kubernetes_pod, + settings->kubernetes_container)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to generate path for Kubernetes API endpoint: " + "Resulting path too long"); + goto fail; + } + + guac_client_log(client, GUAC_LOG_DEBUG, "The endpoint for attaching to " + "the requested Kubernetes pod is \"%s\".", endpoint_path); /* Set up screen recording, if requested */ if (settings->recording_path != NULL) { @@ -434,9 +457,9 @@ void* guac_kubernetes_client_thread(void* data) { goto fail; } - /* FIXME: Generate path dynamically */ + /* Generate path dynamically */ connection_info.context = kubernetes_client->context; - connection_info.path = "/api/v1/namespaces/default/pods/my-shell-68974bb7f7-rpjgr/attach?container=my-shell&stdin=true&stdout=true&tty=true"; + connection_info.path = endpoint_path; /* Open WebSocket connection to Kubernetes */ kubernetes_client->wsi = lws_client_connect_via_info(&connection_info); diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 50daa6f9..4f9f3f24 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -32,6 +32,9 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { "hostname", "port", + "namespace", + "pod", + "container", "use-ssl", "client-cert-file", "client-key-file", @@ -67,6 +70,24 @@ enum KUBERNETES_ARGS_IDX { */ IDX_PORT, + /** + * The name of the Kubernetes namespace of the pod containing the container + * being attached to. If omitted, the default namespace will be used. + */ + IDX_NAMESPACE, + + /** + * The name of the Kubernetes pod containing with the container being + * attached to. Required. + */ + IDX_POD, + + /** + * The name of the container to attach to. If omitted, the first container + * in the pod will be used. + */ + IDX_CONTAINER, + /** * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used. */ @@ -215,11 +236,31 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, guac_kubernetes_settings* settings = calloc(1, sizeof(guac_kubernetes_settings)); - /* Read parameters */ + /* Read hostname */ settings->hostname = guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_HOSTNAME, ""); + /* Read port */ + settings->port = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT); + + /* Read Kubernetes namespace */ + settings->kubernetes_namespace = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_NAMESPACE, GUAC_KUBERNETES_DEFAULT_NAMESPACE); + + /* Read name of Kubernetes pod (required) */ + settings->kubernetes_pod = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_POD, NULL); + + /* Read container of pod (optional) */ + settings->kubernetes_container = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_CONTAINER, NULL); + /* Parse whether SSL should be used */ settings->use_ssl = guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, @@ -276,11 +317,6 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, settings->height = user->info.optimal_height; settings->resolution = user->info.optimal_resolution; - /* Read port */ - settings->port = - guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, - IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT); - /* Read typescript path */ settings->typescript_path = guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, @@ -341,6 +377,11 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { /* Free network connection information */ free(settings->hostname); + /* Free Kubernetes pod/container details */ + free(settings->kubernetes_namespace); + free(settings->kubernetes_pod); + free(settings->kubernetes_container); + /* Free SSL/TLS details */ free(settings->client_cert_file); free(settings->client_key_file); diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index 8f42f8ca..c2e479ea 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -44,6 +44,12 @@ */ #define GUAC_KUBERNETES_DEFAULT_PORT 8080 +/** + * The name of the Kubernetes namespace that should be used by default if no + * specific Kubernetes namespace is provided. + */ +#define GUAC_KUBERNETES_DEFAULT_NAMESPACE "default" + /** * The filename to use for the typescript, if not specified. */ @@ -76,6 +82,24 @@ typedef struct guac_kubernetes_settings { */ int port; + /** + * The name of the Kubernetes namespace of the pod containing the container + * being attached to. + */ + char* kubernetes_namespace; + + /** + * The name of the Kubernetes pod containing with the container being + * attached to. + */ + char* kubernetes_pod; + + /** + * The name of the container to attach to, or NULL to arbitrarily attach to + * the first container in the pod. + */ + char* kubernetes_container; + /** * Whether SSL/TLS should be used. */ diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c new file mode 100644 index 00000000..cfd6f745 --- /dev/null +++ b/src/protocols/kubernetes/url.c @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "url.h" + +#include +#include + +static int guac_kubernetes_is_url_safe(char c) { + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || strchr("-_.!~*'()", c) != NULL; +} + +int guac_kubernetes_escape_url_component(char* output, int length, + const char* str) { + + char* current = output; + while (*str != '\0') { + + char c = *str; + + /* Store alphanumeric characters verbatim */ + if (guac_kubernetes_is_url_safe(c)) { + + /* Verify space exists for single character */ + if (length < 1) + return 1; + + *(current++) = c; + length--; + + } + + /* Escape EVERYTHING else as hex */ + else { + + /* Verify space exists for hex-encoded character */ + if (length < 4) + return 1; + + snprintf(current, 4, "%%%02X", (int) c); + + current += 3; + length -= 3; + } + + /* Next character */ + str++; + + } + + /* Verify space exists for null terminator */ + if (length < 1) + return 1; + + /* Append null terminator */ + *current = '\0'; + return 0; + +} + +int guac_kubernetes_endpoint_attach(char* buffer, int length, + const char* kubernetes_namespace, const char* kubernetes_pod, + const char* kubernetes_container) { + + int written; + + char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; + char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; + char escaped_container[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; + + /* Escape Kubernetes namespace */ + if (guac_kubernetes_escape_url_component(escaped_namespace, + sizeof(escaped_namespace), kubernetes_namespace)) + return 1; + + /* Escape name of Kubernetes pod */ + if (guac_kubernetes_escape_url_component(escaped_pod, + sizeof(escaped_pod), kubernetes_pod)) + return 1; + + /* Generate attachment endpoint URL */ + if (kubernetes_container != NULL) { + + /* Escape container name */ + if (guac_kubernetes_escape_url_component(escaped_container, + sizeof(escaped_container), kubernetes_container)) + return 1; + + written = snprintf(buffer, length, + "/api/v1/namespaces/%s/pods/%s/attach" + "?container=%s&stdin=true&stdout=true&tty=true", + escaped_namespace, escaped_pod, escaped_container); + } + else { + written = snprintf(buffer, length, + "/api/v1/namespaces/%s/pods/%s/attach" + "?stdin=true&stdout=true&tty=true", + escaped_namespace, escaped_pod); + } + + /* Endpoint URL was successfully generated if it was written to the given + * buffer without truncation */ + return !(written < length - 1); + +} + diff --git a/src/protocols/kubernetes/url.h b/src/protocols/kubernetes/url.h new file mode 100644 index 00000000..19084ee0 --- /dev/null +++ b/src/protocols/kubernetes/url.h @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_URL_H +#define GUAC_KUBERNETES_URL_H + +#include "config.h" + +/** + * The maximum number of characters allowed in the full path for any Kubernetes + * endpoint. + */ +#define GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH 1024 + +/** + * Escapes the given string such that it can be included safely within a URL. + * This function duplicates the behavior of JavaScript's encodeURIComponent(), + * escaping all but the following characters: A-Z a-z 0-9 - _ . ! ~ * ' ( ) + * + * @param output + * The buffer which should receive the escaped string. This buffer may be + * touched even if escaping is unsuccessful. + * + * @param length + * The number of bytes available in the given output buffer. + * + * @param str + * The string to escape. + * + * @return + * Zero if the string was successfully escaped and written into the + * provided output buffer without being truncated, including null + * terminator, non-zero otherwise. + */ +int guac_kubernetes_escape_url_component(char* output, int length, + const char* str); + +/** + * Generates the full path to the Kubernetes API endpoint which handles + * attaching to running containers within specific pods. Values within the path + * will be URL-escaped as necessary. + * + * @param buffer + * The buffer which should receive the endpoint path. This buffer may be + * touched even if the endpoint path could not be generated. + * + * @param length + * The number of bytes available in the given buffer. + * + * @param kubernetes_namespace + * The name of the Kubernetes namespace of the pod containing the container + * being attached to. + * + * @param kubernetes_pod + * The name of the Kubernetes pod containing with the container being + * attached to. + * + * @param kubernetes_container + * The name of the container to attach to, or NULL to arbitrarily attach + * to the first container in the pod. + * + * @return + * Zero if the endpoint path was successfully written to the provided + * buffer, non-zero if insufficient space exists within the buffer. + */ +int guac_kubernetes_endpoint_attach(char* buffer, int length, + const char* kubernetes_namespace, const char* kubernetes_pod, + const char* kubernetes_container); + +#endif + From c5f67a31dc6c803da23f70662befc102a9187855 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 20:00:44 -0700 Subject: [PATCH 025/175] GUACAMOLE-623: Add configure test for LWS_CALLBACK_CLIENT_CLOSED (only defined in recent libwebsockets and required if present). --- configure.ac | 10 ++++++++++ src/protocols/kubernetes/kubernetes.c | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index ae783244..7c0bb73c 100644 --- a/configure.ac +++ b/configure.ac @@ -1193,6 +1193,16 @@ then [have_libwebsockets=no]) fi +# Check for client-specific closed event, which must be used in favor of the +# generic closed event if libwebsockets is recent enough to provide this +if test "x$with_websockets" != "xno" +then + AC_CHECK_DECL([LWS_CALLBACK_CLIENT_CLOSED], + [AC_DEFINE([HAVE_LWS_CALLBACK_CLIENT_CLOSED],, + [Whether LWS_CALLBACK_CLIENT_CLOSED is defined])],, + [#include ]) +fi + AM_CONDITIONAL([ENABLE_WEBSOCKETS], [test "x${have_libwebsockets}" = "xyes"]) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 380d1d3b..9850c6d4 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -268,7 +268,6 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, lws_callback_on_writable(wsi); break; - /* TODO: Add configure test */ #ifdef HAVE_LWS_CALLBACK_CLIENT_CLOSED /* Connection closed (client-specific) */ case LWS_CALLBACK_CLIENT_CLOSED: From 77a866129b3b6592f122a5912c349a03f0c4b4e2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 20:09:36 -0700 Subject: [PATCH 026/175] GUACAMOLE-623: Add warning when Kubernetes support will not be built. Fix summary output from configure. --- configure.ac | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 7c0bb73c..df36eabe 100644 --- a/configure.ac +++ b/configure.ac @@ -1190,7 +1190,12 @@ then AC_CHECK_LIB([websockets], [lws_create_context], [WEBSOCKETS_LIBS="$WEBSOCKETS_LIBS -lwebsockets"], - [have_libwebsockets=no]) + [AC_MSG_WARN([ + -------------------------------------------- + Unable to find libwebsockets. + Support for Kubernetes will be disabled. + --------------------------------------------]) + have_libwebsockets=no]) fi # Check for client-specific closed event, which must be used in favor of the @@ -1339,7 +1344,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} - libwebsockets ....... ${have_websockets} + libwebsockets ....... ${have_libwebsockets} libwebp ............. ${have_webp} wsock32 ............. ${have_winsock} From 371eed1f93c353e0bf2159bcffd77cf6d8bd9716 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 21:05:23 -0700 Subject: [PATCH 027/175] GUACAMOLE-623: Add missin includes. Remove unnecessary includes. --- src/protocols/kubernetes/client.c | 9 ++++----- src/protocols/kubernetes/client.h | 6 +----- src/protocols/kubernetes/clipboard.c | 2 -- src/protocols/kubernetes/clipboard.h | 2 -- src/protocols/kubernetes/input.c | 5 +---- src/protocols/kubernetes/input.h | 2 -- src/protocols/kubernetes/kubernetes.c | 10 ++-------- src/protocols/kubernetes/kubernetes.h | 4 +++- src/protocols/kubernetes/pipe.c | 5 +++-- src/protocols/kubernetes/pipe.h | 2 -- src/protocols/kubernetes/settings.c | 5 ----- src/protocols/kubernetes/settings.h | 3 --- src/protocols/kubernetes/url.c | 2 +- src/protocols/kubernetes/url.h | 2 -- src/protocols/kubernetes/user.c | 6 +++--- src/protocols/kubernetes/user.h | 2 -- 16 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 450e1e87..58f4728a 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -17,22 +17,21 @@ * under the License. */ -#include "config.h" #include "client.h" -#include "common/recording.h" +#include "common/clipboard.h" #include "kubernetes.h" #include "settings.h" -#include "terminal/terminal.h" #include "user.h" +#include +#include + #include #include #include #include #include -#include - /** * Static reference to the guac_client associated with the active Kubernetes * connection. As guacd guarantees that each main client connection is diff --git a/src/protocols/kubernetes/client.h b/src/protocols/kubernetes/client.h index 2e96d109..0b847da4 100644 --- a/src/protocols/kubernetes/client.h +++ b/src/protocols/kubernetes/client.h @@ -20,11 +20,7 @@ #ifndef GUAC_KUBERNETES_CLIENT_H #define GUAC_KUBERNETES_CLIENT_H -#include "config.h" -#include "terminal/terminal.h" - -#include -#include +#include /** * The maximum number of bytes to allow within the clipboard. diff --git a/src/protocols/kubernetes/clipboard.c b/src/protocols/kubernetes/clipboard.c index 87a34b04..f1682066 100644 --- a/src/protocols/kubernetes/clipboard.c +++ b/src/protocols/kubernetes/clipboard.c @@ -17,11 +17,9 @@ * under the License. */ -#include "config.h" #include "clipboard.h" #include "common/clipboard.h" #include "kubernetes.h" -#include "terminal/terminal.h" #include #include diff --git a/src/protocols/kubernetes/clipboard.h b/src/protocols/kubernetes/clipboard.h index 009219cf..87a393cf 100644 --- a/src/protocols/kubernetes/clipboard.h +++ b/src/protocols/kubernetes/clipboard.h @@ -20,8 +20,6 @@ #ifndef GUAC_KUBERNETES_CLIPBOARD_H #define GUAC_KUBERNETES_CLIPBOARD_H -#include "config.h" - #include /** diff --git a/src/protocols/kubernetes/input.c b/src/protocols/kubernetes/input.c index e73772e5..814578ef 100644 --- a/src/protocols/kubernetes/input.c +++ b/src/protocols/kubernetes/input.c @@ -17,18 +17,15 @@ * under the License. */ -#include "config.h" #include "common/recording.h" -#include "kubernetes.h" #include "input.h" +#include "kubernetes.h" #include "terminal/terminal.h" #include #include #include -#include -#include int guac_kubernetes_user_mouse_handler(guac_user* user, int x, int y, int mask) { diff --git a/src/protocols/kubernetes/input.h b/src/protocols/kubernetes/input.h index ac65835c..6f24cf20 100644 --- a/src/protocols/kubernetes/input.h +++ b/src/protocols/kubernetes/input.h @@ -20,8 +20,6 @@ #ifndef GUAC_KUBERNETES_INPUT_H #define GUAC_KUBERNETES_INPUT_H -#include "config.h" - #include /** diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 9850c6d4..3644d6ec 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -18,8 +18,8 @@ */ #include "config.h" -#include "common/recording.h" #include "kubernetes.h" +#include "common/recording.h" #include "terminal/terminal.h" #include "url.h" @@ -27,16 +27,10 @@ #include #include -#include -#include -#include -#include #include +#include #include #include -#include -#include -#include /** * Handles data received from Kubernetes over WebSocket, decoding the channel diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index fedf77a2..8c9a25e7 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -20,13 +20,15 @@ #ifndef GUAC_KUBERNETES_H #define GUAC_KUBERNETES_H -#include "config.h" #include "common/clipboard.h" #include "common/recording.h" #include "settings.h" #include "terminal/terminal.h" +#include #include + +#include #include /** diff --git a/src/protocols/kubernetes/pipe.c b/src/protocols/kubernetes/pipe.c index 242105b1..8f18530a 100644 --- a/src/protocols/kubernetes/pipe.c +++ b/src/protocols/kubernetes/pipe.c @@ -17,12 +17,13 @@ * under the License. */ -#include "config.h" #include "kubernetes.h" -#include "pipe.h" #include "terminal/terminal.h" +#include "pipe.h" +#include #include +#include #include #include diff --git a/src/protocols/kubernetes/pipe.h b/src/protocols/kubernetes/pipe.h index 7acae3cb..47565bfd 100644 --- a/src/protocols/kubernetes/pipe.h +++ b/src/protocols/kubernetes/pipe.h @@ -21,8 +21,6 @@ #ifndef GUAC_KUBERNETES_PIPE_H #define GUAC_KUBERNETES_PIPE_H -#include "config.h" - #include /** diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 4f9f3f24..122a8584 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -17,16 +17,11 @@ * under the License. */ -#include "config.h" - #include "settings.h" #include -#include #include -#include -#include /* Client plugin arguments */ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index c2e479ea..a86d14a5 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -20,11 +20,8 @@ #ifndef GUAC_KUBERNETES_SETTINGS_H #define GUAC_KUBERNETES_SETTINGS_H -#include "config.h" - #include -#include #include /** diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c index cfd6f745..434cc87e 100644 --- a/src/protocols/kubernetes/url.c +++ b/src/protocols/kubernetes/url.c @@ -17,10 +17,10 @@ * under the License. */ -#include "config.h" #include "url.h" #include +#include #include static int guac_kubernetes_is_url_safe(char c) { diff --git a/src/protocols/kubernetes/url.h b/src/protocols/kubernetes/url.h index 19084ee0..285baa21 100644 --- a/src/protocols/kubernetes/url.h +++ b/src/protocols/kubernetes/url.h @@ -20,8 +20,6 @@ #ifndef GUAC_KUBERNETES_URL_H #define GUAC_KUBERNETES_URL_H -#include "config.h" - /** * The maximum number of characters allowed in the full path for any Kubernetes * endpoint. diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c index 62666cb5..f90260e7 100644 --- a/src/protocols/kubernetes/user.c +++ b/src/protocols/kubernetes/user.c @@ -17,9 +17,8 @@ * under the License. */ -#include "config.h" - #include "clipboard.h" +#include "common/cursor.h" #include "input.h" #include "kubernetes.h" #include "pipe.h" @@ -28,11 +27,12 @@ #include "user.h" #include +#include #include #include #include -#include +#include int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { diff --git a/src/protocols/kubernetes/user.h b/src/protocols/kubernetes/user.h index d235b2b7..55d49fdf 100644 --- a/src/protocols/kubernetes/user.h +++ b/src/protocols/kubernetes/user.h @@ -20,8 +20,6 @@ #ifndef GUAC_KUBERNETES_USER_H #define GUAC_KUBERNETES_USER_H -#include "config.h" - #include /** From 5e3aec6df2ec3578e47030665f7b12c3c3cadd51 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 21:08:19 -0700 Subject: [PATCH 028/175] GUACAMOLE-623: Add missing documentation for URL character test. --- src/protocols/kubernetes/url.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c index 434cc87e..78c116e5 100644 --- a/src/protocols/kubernetes/url.c +++ b/src/protocols/kubernetes/url.c @@ -23,6 +23,17 @@ #include #include +/** + * Returns whether the given character is a character that need not be + * escaped when included as part of a component of a URL. + * + * @param c + * The character to test. + * + * @return + * Zero if the character does not need to be escaped when included as + * part of a component of a URL, non-zero otherwise. + */ static int guac_kubernetes_is_url_safe(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') From 2e50573531411a49c98dcb44a29fe2ad8a983609 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 10 Sep 2018 22:55:02 -0700 Subject: [PATCH 029/175] GUACAMOLE-623: Move I/O-related functions into separate files. --- src/protocols/kubernetes/Makefile.am | 2 + src/protocols/kubernetes/io.c | 143 ++++++++++++++++++++++ src/protocols/kubernetes/io.h | 144 ++++++++++++++++++++++ src/protocols/kubernetes/kubernetes.c | 165 +------------------------- src/protocols/kubernetes/kubernetes.h | 59 +-------- 5 files changed, 292 insertions(+), 221 deletions(-) create mode 100644 src/protocols/kubernetes/io.c create mode 100644 src/protocols/kubernetes/io.h diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index 9e50feb6..e818ff72 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -26,6 +26,7 @@ libguac_client_kubernetes_la_SOURCES = \ client.c \ clipboard.c \ input.c \ + io.c \ pipe.c \ settings.c \ kubernetes.c \ @@ -36,6 +37,7 @@ noinst_HEADERS = \ client.h \ clipboard.h \ input.h \ + io.h \ pipe.h \ settings.h \ kubernetes.h \ diff --git a/src/protocols/kubernetes/io.c b/src/protocols/kubernetes/io.c new file mode 100644 index 00000000..bfa37b1d --- /dev/null +++ b/src/protocols/kubernetes/io.c @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include + +void guac_kubernetes_receive_data(guac_client* client, + const char* buffer, size_t length) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Strip channel index from beginning of buffer */ + int channel = *(buffer++); + length--; + + switch (channel) { + + /* Write STDOUT / STDERR directly to terminal as output */ + case GUAC_KUBERNETES_CHANNEL_STDOUT: + case GUAC_KUBERNETES_CHANNEL_STDERR: + guac_terminal_write(kubernetes_client->term, buffer, length); + break; + + /* Ignore data on other channels */ + default: + guac_client_log(client, GUAC_LOG_DEBUG, "Received %i bytes along " + "channel %i.", length, channel); + + } + +} + +void guac_kubernetes_send_message(guac_client* client, + int channel, const char* data, int length) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); + + /* Add message to buffer if space is available */ + if (kubernetes_client->outbound_messages_waiting + < GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES) { + + /* Calculate storage position of next message */ + int index = (kubernetes_client->outbound_messages_top + + kubernetes_client->outbound_messages_waiting) + % GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; + + /* Obtain pointer to message slot at calculated position */ + guac_kubernetes_message* message = + &(kubernetes_client->outbound_messages[index]); + + /* Copy details of message into buffer */ + message->channel = channel; + memcpy(message->data, data, length); + message->length = length; + + /* One more message is now waiting */ + kubernetes_client->outbound_messages_waiting++; + + /* Notify libwebsockets that we need a callback to send pending + * messages */ + lws_callback_on_writable(kubernetes_client->wsi); + lws_cancel_service(kubernetes_client->context); + + } + + /* Warn if data has to be dropped */ + else + guac_client_log(client, GUAC_LOG_WARNING, "Send buffer could not be " + "flushed in time to handle additional data. Outbound " + "message dropped."); + + pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); + +} + +bool guac_kubernetes_write_pending_message(guac_client* client) { + + bool messages_remain; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); + + /* Send one message from top of buffer */ + if (kubernetes_client->outbound_messages_waiting > 0) { + + /* Obtain pointer to message at top */ + int top = kubernetes_client->outbound_messages_top; + guac_kubernetes_message* message = + &(kubernetes_client->outbound_messages[top]); + + /* Write message including channel index */ + lws_write(kubernetes_client->wsi, + ((unsigned char*) message) + LWS_PRE, + message->length + 1, LWS_WRITE_BINARY); + + /* Advance top to next message */ + kubernetes_client->outbound_messages_top++; + kubernetes_client->outbound_messages_top %= + GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; + + /* One less message is waiting */ + kubernetes_client->outbound_messages_waiting--; + + } + + /* Record whether messages remained at time of completion */ + messages_remain = (kubernetes_client->outbound_messages_waiting > 0); + + pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); + + return messages_remain; + +} + + diff --git a/src/protocols/kubernetes/io.h b/src/protocols/kubernetes/io.h new file mode 100644 index 00000000..40f2c69a --- /dev/null +++ b/src/protocols/kubernetes/io.h @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_IO_H +#define GUAC_KUBERNETES_IO_H + +#include +#include + +#include +#include + +/** + * The maximum amount of data to include in any particular WebSocket message + * to Kubernetes. This excludes the storage space required for the channel + * index. + */ +#define GUAC_KUBERNETES_MAX_MESSAGE_SIZE 1024 + +/** + * The index of the Kubernetes channel used for STDIN. + */ +#define GUAC_KUBERNETES_CHANNEL_STDIN 0 + +/** + * The index of the Kubernetes channel used for STDOUT. + */ +#define GUAC_KUBERNETES_CHANNEL_STDOUT 1 + +/** + * The index of the Kubernetes channel used for STDERR. + */ +#define GUAC_KUBERNETES_CHANNEL_STDERR 2 + +/** + * The index of the Kubernetes channel used for terminal resize messages. + */ +#define GUAC_KUBERNETES_CHANNEL_RESIZE 4 + +/** + * An outbound message to be received by Kubernetes over WebSocket. + */ +typedef struct guac_kubernetes_message { + + /** + * lws_write() requires leading padding of LWS_PRE bytes to provide + * scratch space for WebSocket framing. + */ + uint8_t _padding[LWS_PRE]; + + /** + * The index of the channel receiving the data, such as + * GUAC_KUBERNETES_CHANNEL_STDIN. + */ + uint8_t channel; + + /** + * The data that should be sent to Kubernetes (along with the channel + * index). + */ + char data[GUAC_KUBERNETES_MAX_MESSAGE_SIZE]; + + /** + * The length of the data to be sent, excluding the channel index. + */ + int length; + +} guac_kubernetes_message; + + +/** + * Handles data received from Kubernetes over WebSocket, decoding the channel + * index of the received data and forwarding that data accordingly. + * + * @param client + * The guac_client associated with the connection to Kubernetes. + * + * @param buffer + * The data received from Kubernetes. + * + * @param length + * The size of the data received from Kubernetes, in bytes. + */ +void guac_kubernetes_receive_data(guac_client* client, + const char* buffer, size_t length); + +/** + * Requests that the given data be sent along the given channel to the + * Kubernetes server when the WebSocket connection is next available for + * writing. If the WebSocket connection has not been available for writing for + * long enough that the outbound message buffer is full, the request to send + * this particular message will be dropped. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @param channel + * The Kubernetes channel on which to send the message, + * such as GUAC_KUBERNETES_CHANNEL_STDIN. + * + * @param data + * A buffer containing the data to send. + * + * @param length + * The number of bytes to send. + */ +void guac_kubernetes_send_message(guac_client* client, + int channel, const char* data, int length); + +/** + * Writes the oldest pending message within the outbound message queue, + * as scheduled with guac_kubernetes_send_message(), removing that message + * from the queue. This function MAY NOT be invoked outside the libwebsockets + * event callback and MUST only be invoked in the context of a + * LWS_CALLBACK_CLIENT_WRITEABLE event. If no messages are pending, this + * function has no effect. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @return + * true if messages still remain to be written within the outbound message + * queue, false otherwise. + */ +bool guac_kubernetes_write_pending_message(guac_client* client); + +#endif + diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 3644d6ec..4e7928ed 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -18,8 +18,9 @@ */ #include "config.h" -#include "kubernetes.h" #include "common/recording.h" +#include "io.h" +#include "kubernetes.h" #include "terminal/terminal.h" #include "url.h" @@ -30,168 +31,6 @@ #include #include #include -#include - -/** - * Handles data received from Kubernetes over WebSocket, decoding the channel - * index of the received data and forwarding that data accordingly. - * - * @param client - * The guac_client associated with the connection to Kubernetes. - * - * @param buffer - * The data received from Kubernetes. - * - * @param length - * The size of the data received from Kubernetes, in bytes. - */ -static void guac_kubernetes_receive_data(guac_client* client, - const char* buffer, size_t length) { - - guac_kubernetes_client* kubernetes_client = - (guac_kubernetes_client*) client->data; - - /* Strip channel index from beginning of buffer */ - int channel = *(buffer++); - length--; - - switch (channel) { - - /* Write STDOUT / STDERR directly to terminal as output */ - case GUAC_KUBERNETES_CHANNEL_STDOUT: - case GUAC_KUBERNETES_CHANNEL_STDERR: - guac_terminal_write(kubernetes_client->term, buffer, length); - break; - - /* Ignore data on other channels */ - default: - guac_client_log(client, GUAC_LOG_DEBUG, "Received %i bytes along " - "channel %i.", length, channel); - - } - -} - -/** - * Requests that the given data be sent along the given channel to the - * Kubernetes server when the WebSocket connection is next available for - * writing. If the WebSocket connection has not been available for writing for - * long enough that the outbound message buffer is full, the request to send - * this particular message will be dropped. - * - * @param client - * The guac_client associated with the Kubernetes connection. - * - * @param channel - * The Kubernetes channel on which to send the message, - * such as GUAC_KUBERNETES_CHANNEL_STDIN. - * - * @param data - * A buffer containing the data to send. - * - * @param length - * The number of bytes to send. - */ -static void guac_kubernetes_send_message(guac_client* client, - int channel, const char* data, int length) { - - guac_kubernetes_client* kubernetes_client = - (guac_kubernetes_client*) client->data; - - pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); - - /* Add message to buffer if space is available */ - if (kubernetes_client->outbound_messages_waiting - < GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES) { - - /* Calculate storage position of next message */ - int index = (kubernetes_client->outbound_messages_top - + kubernetes_client->outbound_messages_waiting) - % GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; - - /* Obtain pointer to message slot at calculated position */ - guac_kubernetes_message* message = - &(kubernetes_client->outbound_messages[index]); - - /* Copy details of message into buffer */ - message->channel = channel; - memcpy(message->data, data, length); - message->length = length; - - /* One more message is now waiting */ - kubernetes_client->outbound_messages_waiting++; - - /* Notify libwebsockets that we need a callback to send pending - * messages */ - lws_callback_on_writable(kubernetes_client->wsi); - lws_cancel_service(kubernetes_client->context); - - } - - /* Warn if data has to be dropped */ - else - guac_client_log(client, GUAC_LOG_WARNING, "Send buffer could not be " - "flushed in time to handle additional data. Outbound " - "message dropped."); - - pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); - -} - -/** - * Writes the oldest pending message within the outbound message queue, - * as scheduled with guac_kubernetes_send_message(), removing that message - * from the queue. This function MAY NOT be invoked outside the libwebsockets - * event callback and MUST only be invoked in the context of a - * LWS_CALLBACK_CLIENT_WRITEABLE event. If no messages are pending, this - * function has no effect. - * - * @param client - * The guac_client associated with the Kubernetes connection. - * - * @return - * true if messages still remain to be written within the outbound message - * queue, false otherwise. - */ -static bool guac_kubernetes_write_pending_message(guac_client* client) { - - bool messages_remain; - guac_kubernetes_client* kubernetes_client = - (guac_kubernetes_client*) client->data; - - pthread_mutex_lock(&(kubernetes_client->outbound_message_lock)); - - /* Send one message from top of buffer */ - if (kubernetes_client->outbound_messages_waiting > 0) { - - /* Obtain pointer to message at top */ - int top = kubernetes_client->outbound_messages_top; - guac_kubernetes_message* message = - &(kubernetes_client->outbound_messages[top]); - - /* Write message including channel index */ - lws_write(kubernetes_client->wsi, - ((unsigned char*) message) + LWS_PRE, - message->length + 1, LWS_WRITE_BINARY); - - /* Advance top to next message */ - kubernetes_client->outbound_messages_top++; - kubernetes_client->outbound_messages_top %= - GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES; - - /* One less message is waiting */ - kubernetes_client->outbound_messages_waiting--; - - } - - /* Record whether messages remained at time of completion */ - messages_remain = (kubernetes_client->outbound_messages_waiting > 0); - - pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock)); - - return messages_remain; - -} /** * Callback invoked by libwebsockets for events related to a WebSocket being diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h index 8c9a25e7..c37ca4cf 100644 --- a/src/protocols/kubernetes/kubernetes.h +++ b/src/protocols/kubernetes/kubernetes.h @@ -22,6 +22,7 @@ #include "common/clipboard.h" #include "common/recording.h" +#include "io.h" #include "settings.h" #include "terminal/terminal.h" @@ -29,7 +30,6 @@ #include #include -#include /** * The name of the WebSocket protocol specific to Kubernetes which should be @@ -37,33 +37,6 @@ */ #define GUAC_KUBERNETES_LWS_PROTOCOL "v4.channel.k8s.io" -/** - * The index of the Kubernetes channel used for STDIN. - */ -#define GUAC_KUBERNETES_CHANNEL_STDIN 0 - -/** - * The index of the Kubernetes channel used for STDOUT. - */ -#define GUAC_KUBERNETES_CHANNEL_STDOUT 1 - -/** - * The index of the Kubernetes channel used for STDERR. - */ -#define GUAC_KUBERNETES_CHANNEL_STDERR 2 - -/** - * The index of the Kubernetes channel used for terminal resize messages. - */ -#define GUAC_KUBERNETES_CHANNEL_RESIZE 4 - -/** - * The maximum amount of data to include in any particular WebSocket message - * to Kubernetes. This excludes the storage space required for the channel - * index. - */ -#define GUAC_KUBERNETES_MAX_MESSAGE_SIZE 1024 - /** * The maximum number of messages to allow within the outbound message buffer. * If messages are sent despite the buffer being full, those messages will be @@ -77,36 +50,6 @@ */ #define GUAC_KUBERNETES_SERVICE_INTERVAL 1000 -/** - * An outbound message to be received by Kubernetes over WebSocket. - */ -typedef struct guac_kubernetes_message { - - /** - * lws_write() requires leading padding of LWS_PRE bytes to provide - * scratch space for WebSocket framing. - */ - uint8_t _padding[LWS_PRE]; - - /** - * The index of the channel receiving the data, such as - * GUAC_KUBERNETES_CHANNEL_STDIN. - */ - uint8_t channel; - - /** - * The data that should be sent to Kubernetes (along with the channel - * index). - */ - char data[GUAC_KUBERNETES_MAX_MESSAGE_SIZE]; - - /** - * The length of the data to be sent, excluding the channel index. - */ - int length; - -} guac_kubernetes_message; - /** * Kubernetes-specific client data. */ From 83a531bc89a5c79371f42d1ec5142c484a08479a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Sep 2018 03:03:17 -0700 Subject: [PATCH 030/175] GUACAMOLE-623: Add support for SSL. --- configure.ac | 1 + src/protocols/kubernetes/Makefile.am | 3 + src/protocols/kubernetes/client.c | 16 +- src/protocols/kubernetes/client.h | 7 + src/protocols/kubernetes/kubernetes.c | 46 +++--- src/protocols/kubernetes/settings.c | 48 +++--- src/protocols/kubernetes/settings.h | 24 +-- src/protocols/kubernetes/ssl.c | 210 ++++++++++++++++++++++++++ src/protocols/kubernetes/ssl.h | 41 +++++ 9 files changed, 326 insertions(+), 70 deletions(-) create mode 100644 src/protocols/kubernetes/ssl.c create mode 100644 src/protocols/kubernetes/ssl.h diff --git a/configure.ac b/configure.ac index df36eabe..d26db39f 100644 --- a/configure.ac +++ b/configure.ac @@ -1225,6 +1225,7 @@ AC_ARG_ENABLE([kubernetes], AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \ -a "x${have_libwebsockets}" = "xyes" \ + -a "x${have_ssl}" = "xyes" \ -a "x${have_terminal}" = "xyes"]) # diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index e818ff72..56db4d64 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -29,6 +29,7 @@ libguac_client_kubernetes_la_SOURCES = \ io.c \ pipe.c \ settings.c \ + ssl.c \ kubernetes.c \ url.c \ user.c @@ -40,6 +41,7 @@ noinst_HEADERS = \ io.h \ pipe.h \ settings.h \ + ssl.h \ kubernetes.h \ url.h \ user.h @@ -57,5 +59,6 @@ libguac_client_kubernetes_la_LIBADD = \ libguac_client_kubernetes_la_LDFLAGS = \ -version-info 0:0:0 \ @PTHREAD_LIBS@ \ + @SSL_LIBS@ \ @WEBSOCKETS_LIBS@ diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 58f4728a..331e03d7 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -32,12 +32,7 @@ #include #include -/** - * Static reference to the guac_client associated with the active Kubernetes - * connection. As guacd guarantees that each main client connection is - * isolated within its own process, this is safe. - */ -static guac_client* guac_kubernetes_lws_log_client = NULL; +guac_client* guac_kubernetes_lws_current_client = NULL; /** * Logging callback invoked by libwebsockets to log a single line of logging @@ -53,15 +48,18 @@ static guac_client* guac_kubernetes_lws_log_client = NULL; * The line of logging output to log. */ static void guac_kubernetes_log(int level, const char* line) { - if (guac_kubernetes_lws_log_client != NULL) - guac_client_log(guac_kubernetes_lws_log_client, GUAC_LOG_DEBUG, + if (guac_kubernetes_lws_current_client != NULL) + guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG, "libwebsockets: %s", line); } int guac_client_init(guac_client* client) { + /* Ensure reference to main guac_client remains available in all + * libwebsockets contexts */ + guac_kubernetes_lws_current_client = client; + /* Redirect libwebsockets logging */ - guac_kubernetes_lws_log_client = client; lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, guac_kubernetes_log); diff --git a/src/protocols/kubernetes/client.h b/src/protocols/kubernetes/client.h index 0b847da4..ec4ba326 100644 --- a/src/protocols/kubernetes/client.h +++ b/src/protocols/kubernetes/client.h @@ -27,6 +27,13 @@ */ #define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144 +/** + * Static reference to the guac_client associated with the active Kubernetes + * connection. While libwebsockets provides some means of storing and + * retrieving custom data in some structures, this is not always available. + */ +extern guac_client* guac_kubernetes_lws_current_client; + /** * Free handler. Required by libguac and called when the guac_client is * disconnected and must be cleaned up. diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 4e7928ed..f314c597 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -18,9 +18,11 @@ */ #include "config.h" +#include "client.h" #include "common/recording.h" #include "io.h" #include "kubernetes.h" +#include "ssl.h" #include "terminal/terminal.h" #include "url.h" @@ -43,8 +45,9 @@ * The reason (event) that this callback was invoked. * * @param user - * Arbitrary data assocated with the WebSocket session. This will always - * be a pointer to the guac_client instance. + * Arbitrary data assocated with the WebSocket session. In some cases, + * this is actually event-specific data (such as the + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERT event). * * @param in * A pointer to arbitrary, reason-specific data. @@ -60,14 +63,19 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t length) { - /* Request connection closure if client is stopped (note that the user - * pointer passed by libwebsockets may be NULL for some events) */ - guac_client* client = (guac_client*) user; - if (client != NULL && client->state != GUAC_CLIENT_RUNNING) + guac_client* client = guac_kubernetes_lws_current_client; + + /* Do not handle any further events if connection is closing */ + if (client->state != GUAC_CLIENT_RUNNING) return lws_callback_http_dummy(wsi, reason, user, in, length); switch (reason) { + /* Complete initialization of SSL */ + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: + guac_kubernetes_init_ssl(client, (SSL_CTX*) user); + break; + /* Failed to connect */ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, @@ -256,29 +264,13 @@ void* guac_kubernetes_client_thread(void* data) { }; /* If requested, use an SSL/TLS connection for communication with - * Kubernetes */ + * Kubernetes. Note that we disable hostname checks here because we + * do our own validation - libwebsockets does not validate properly if + * IP addresses are used. */ if (settings->use_ssl) { - - /* Enable use of SSL/TLS */ context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; - connection_info.ssl_connection = LCCSCF_USE_SSL; - - /* Bypass certificate checks if requested */ - if (settings->ignore_cert) { - connection_info.ssl_connection |= - LCCSCF_ALLOW_SELFSIGNED - | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK - | LCCSCF_ALLOW_EXPIRED; - } - - /* Otherwise use the given CA certificate to validate (if any) */ - else - context_info.client_ssl_ca_filepath = settings->ca_cert_file; - - /* Certificate and key file for SSL/TLS client auth */ - context_info.client_ssl_cert_filepath = settings->client_cert_file; - context_info.client_ssl_private_key_filepath = settings->client_key_file; - + connection_info.ssl_connection = LCCSCF_USE_SSL + | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; } /* Create libwebsockets context */ diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 122a8584..4f00a445 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -31,9 +31,9 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { "pod", "container", "use-ssl", - "client-cert-file", - "client-key-file", - "ca-cert-file", + "client-cert", + "client-key", + "ca-cert", "ignore-cert", "font-name", "font-size", @@ -89,24 +89,26 @@ enum KUBERNETES_ARGS_IDX { IDX_USE_SSL, /** - * The filename of the certificate to use if performing SSL/TLS client - * authentication to authenticate with the Kubernetes server. If omitted, - * SSL client authentication will not be performed. + * The certificate to use if performing SSL/TLS client authentication to + * authenticate with the Kubernetes server, in PEM format. If omitted, SSL + * client authentication will not be performed. */ - IDX_CLIENT_CERT_FILE, + IDX_CLIENT_CERT, /** - * The filename of the key to use if performing SSL/TLS client - * authentication to authenticate with the Kubernetes server. If omitted, - * SSL client authentication will not be performed. + * The key to use if performing SSL/TLS client authentication to + * authenticate with the Kubernetes server, in PEM format. If omitted, SSL + * client authentication will not be performed. */ - IDX_CLIENT_KEY_FILE, + IDX_CLIENT_KEY, /** - * The filename of the certificate of the certificate authority that signed - * the certificate of the Kubernetes server. + * The certificate of the certificate authority that signed the certificate + * of the Kubernetes server, in PEM format. If omitted. verification of + * the Kubernetes server certificate will use the systemwide certificate + * authorities. */ - IDX_CA_CERT_FILE, + IDX_CA_CERT, /** * Whether the certificate used by the Kubernetes server for SSL/TLS should @@ -264,17 +266,17 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, /* Read SSL/TLS connection details only if enabled */ if (settings->use_ssl) { - settings->client_cert_file = + settings->client_cert = guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, - argv, IDX_CLIENT_CERT_FILE, NULL); + argv, IDX_CLIENT_CERT, NULL); - settings->client_key_file = + settings->client_key = guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, - argv, IDX_CLIENT_KEY_FILE, NULL); + argv, IDX_CLIENT_KEY, NULL); - settings->ca_cert_file = + settings->ca_cert = guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, - argv, IDX_CA_CERT_FILE, NULL); + argv, IDX_CA_CERT, NULL); settings->ignore_cert = guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, @@ -378,9 +380,9 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { free(settings->kubernetes_container); /* Free SSL/TLS details */ - free(settings->client_cert_file); - free(settings->client_key_file); - free(settings->ca_cert_file); + free(settings->client_cert); + free(settings->client_key); + free(settings->ca_cert); /* Free display preferences */ free(settings->font_name); diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index a86d14a5..6267a18b 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -103,24 +103,26 @@ typedef struct guac_kubernetes_settings { bool use_ssl; /** - * The filename of the certificate to use if performing SSL/TLS client - * authentication to authenticate with the Kubernetes server. If omitted, - * SSL client authentication will not be performed. + * The certificate to use if performing SSL/TLS client authentication to + * authenticate with the Kubernetes server, in PEM format. If omitted, SSL + * client authentication will not be performed. */ - char* client_cert_file; + char* client_cert; /** - * The filename of the key to use if performing SSL/TLS client - * authentication to authenticate with the Kubernetes server. If omitted, - * SSL client authentication will not be performed. + * The key to use if performing SSL/TLS client authentication to + * authenticate with the Kubernetes server, in PEM format. If omitted, SSL + * client authentication will not be performed. */ - char* client_key_file; + char* client_key; /** - * The filename of the certificate of the certificate authority that signed - * the certificate of the Kubernetes server. + * The certificate of the certificate authority that signed the certificate + * of the Kubernetes server, in PEM format. If omitted. verification of + * the Kubernetes server certificate will use the systemwide certificate + * authorities. */ - char* ca_cert_file; + char* ca_cert; /** * Whether the certificate used by the Kubernetes server for SSL/TLS should diff --git a/src/protocols/kubernetes/ssl.c b/src/protocols/kubernetes/ssl.c new file mode 100644 index 00000000..6ebafc61 --- /dev/null +++ b/src/protocols/kubernetes/ssl.c @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "kubernetes.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * Tests whether the given hostname is, in fact, an IP address. + * + * @param hostname + * The hostname to test. + * + * @return + * Non-zero if the given hostname is an IP address, zero otherwise. + */ +static int guac_kubernetes_is_address(const char* hostname) { + + /* Attempt to interpret the hostname as an IP address */ + ASN1_OCTET_STRING* ip = a2i_IPADDRESS(hostname); + + /* If unsuccessful, the hostname is not an IP address */ + if (ip == NULL) + return 0; + + /* Converted hostname must be freed */ + ASN1_OCTET_STRING_free(ip); + return 1; + +} + +/** + * Parses the given PEM certificate, returning a new OpenSSL X509 structure + * representing that certificate. + * + * @param pem + * The PEM certificate. + * + * @return + * An X509 structure representing the given certificate, or NULL if the + * certificate was unreadable. + */ +static X509* guac_kubernetes_read_cert(char* pem) { + + /* Prepare a BIO which provides access to the in-memory CA cert */ + BIO* bio = BIO_new_mem_buf(pem, -1); + if (bio == NULL) + return NULL; + + /* Read the CA cert as PEM */ + X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (certificate == NULL) { + BIO_free(bio); + return NULL; + } + + return certificate; + +} + +/** + * Parses the given PEM private key, returning a new OpenSSL EVP_PKEY structure + * representing that key. + * + * @param pem + * The PEM private key. + * + * @return + * An EVP_KEY representing the given private key, or NULL if the private + * key was unreadable. + */ +static EVP_PKEY* guac_kubernetes_read_key(char* pem) { + + /* Prepare a BIO which provides access to the in-memory key */ + BIO* bio = BIO_new_mem_buf(pem, -1); + if (bio == NULL) + return NULL; + + /* Read the private key as PEM */ + EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (key == NULL) { + BIO_free(bio); + return NULL; + } + + return key; + +} + +void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + guac_kubernetes_settings* settings = kubernetes_client->settings; + + /* Bypass certificate checks if requested */ + if (settings->ignore_cert) + SSL_CTX_set_verify(context, SSL_VERIFY_NONE, NULL); + + /* Otherwise use the given CA certificate to validate (if any) */ + else if (settings->ca_cert != NULL) { + + /* Read CA certificate from configuration data */ + X509* ca_cert = guac_kubernetes_read_cert(settings->ca_cert); + if (ca_cert == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Provided CA certificate is unreadable"); + return; + } + + /* Add certificate to CA store */ + X509_STORE* ca_store = SSL_CTX_get_cert_store(context); + if (!X509_STORE_add_cert(ca_store, ca_cert)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to add CA certificate to certificate store of " + "SSL context"); + return; + } + + } + + /* Certificate for SSL/TLS client auth */ + if (settings->client_cert != NULL) { + + /* Read client certificate from configuration data */ + X509* client_cert = guac_kubernetes_read_cert(settings->client_cert); + if (client_cert == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Provided client certificate is unreadable"); + return; + } + + /* Use parsed certificate for authentication */ + if (!SSL_CTX_use_certificate(context, client_cert)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Client certificate could not be used for SSL/TLS " + "client authentication"); + return; + } + + } + + /* Private key for SSL/TLS client auth */ + if (settings->client_key != NULL) { + + /* Read client private key from configuration data */ + EVP_PKEY* client_key = guac_kubernetes_read_key(settings->client_key); + if (client_key == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Provided client private key is unreadable"); + return; + } + + /* Use parsed key for authentication */ + if (!SSL_CTX_use_PrivateKey(context, client_key)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Client private key could not be used for SSL/TLS " + "client authentication"); + return; + } + + } + + /* Enable hostname checking */ + X509_VERIFY_PARAM *param = SSL_CTX_get0_param(context); + X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + + /* Validate properly depending on whether hostname is an IP address */ + if (guac_kubernetes_is_address(settings->hostname)) { + if (!X509_VERIFY_PARAM_set1_ip_asc(param, settings->hostname)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Server IP address validation could not be enabled"); + return; + } + } + else { + if (!X509_VERIFY_PARAM_set1_host(param, settings->hostname, 0)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Server hostname validation could not be enabled"); + return; + } + } + +} + diff --git a/src/protocols/kubernetes/ssl.h b/src/protocols/kubernetes/ssl.h new file mode 100644 index 00000000..cca02bdb --- /dev/null +++ b/src/protocols/kubernetes/ssl.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_KUBERNETES_SSL_H +#define GUAC_KUBERNETES_SSL_H + +#include "settings.h" + +#include + +/** + * Initializes the given SSL/TLS context using the configuration parameters + * associated with the given guac_client, setting up hostname/address + * validation and client authentication. + * + * @param client + * The guac_client associated with the Kubernetes connection. + * + * @param context + * The SSL_CTX in use by libwebsockets. + */ +void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context); + +#endif + From 61df2956b322a01a777c8169cbbd2b2a2877d696 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Sep 2018 03:17:00 -0700 Subject: [PATCH 031/175] GUACAMOLE-623: Clean up logging (libwebsockets adds newline characters). --- src/protocols/kubernetes/client.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c index 331e03d7..1a1eb3a7 100644 --- a/src/protocols/kubernetes/client.c +++ b/src/protocols/kubernetes/client.c @@ -48,9 +48,32 @@ guac_client* guac_kubernetes_lws_current_client = NULL; * The line of logging output to log. */ static void guac_kubernetes_log(int level, const char* line) { - if (guac_kubernetes_lws_current_client != NULL) - guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG, - "libwebsockets: %s", line); + + char buffer[1024]; + + /* Drop log message if there's nowhere to log yet */ + if (guac_kubernetes_lws_current_client == NULL) + return; + + /* Trim length of line to fit buffer (plus null terminator) */ + int length = strlen(line); + if (length > sizeof(buffer) - 1) + length = sizeof(buffer) - 1; + + /* Copy as much of the received line as will fit in the buffer */ + memcpy(buffer, line, length); + + /* If the line ends with a newline character, trim the character */ + if (length > 0 && buffer[length - 1] == '\n') + length--; + + /* Null-terminate the trimmed string */ + buffer[length] = '\0'; + + /* Log using guacd's own log facilities */ + guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG, + "libwebsockets: %s", buffer); + } int guac_client_init(guac_client* client) { From acfc759527713fcfb5d18f222aa424a8a60548de Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 09:38:22 -0700 Subject: [PATCH 032/175] GUACAMOLE-623: Include Kubernetes plugin in dist directories. --- Makefile.am | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Makefile.am b/Makefile.am index 91c8abec..63bd8e08 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,19 +20,20 @@ ACLOCAL_AMFLAGS = -I m4 # Subprojects -DIST_SUBDIRS = \ - src/libguac \ - src/common \ - src/common-ssh \ - src/terminal \ - src/guacd \ - src/guacenc \ - src/guaclog \ - src/pulse \ - src/protocols/rdp \ - src/protocols/ssh \ - src/protocols/telnet \ - src/protocols/vnc \ +DIST_SUBDIRS = \ + src/libguac \ + src/common \ + src/common-ssh \ + src/terminal \ + src/guacd \ + src/guacenc \ + src/guaclog \ + src/pulse \ + src/protocols/kubernetes \ + src/protocols/rdp \ + src/protocols/ssh \ + src/protocols/telnet \ + src/protocols/vnc \ tests SUBDIRS = \ From d8618b0682e69ca5c99f0608368f21188d16fce3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 21:50:19 -0700 Subject: [PATCH 033/175] GUACAMOLE-623: Support older libwebsockets SSL initialization. --- configure.ac | 22 ++++++++++++++++++++-- src/protocols/kubernetes/kubernetes.c | 6 ++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index d26db39f..bb23f62c 100644 --- a/configure.ac +++ b/configure.ac @@ -1198,14 +1198,32 @@ then have_libwebsockets=no]) fi -# Check for client-specific closed event, which must be used in favor of the -# generic closed event if libwebsockets is recent enough to provide this if test "x$with_websockets" != "xno" then + + # Check for client-specific closed event, which must be used in favor of + # the generic closed event if libwebsockets is recent enough to provide + # this AC_CHECK_DECL([LWS_CALLBACK_CLIENT_CLOSED], [AC_DEFINE([HAVE_LWS_CALLBACK_CLIENT_CLOSED],, [Whether LWS_CALLBACK_CLIENT_CLOSED is defined])],, [#include ]) + + # Older versions of libwebsockets may not define a flag for requesting + # global initialization of OpenSSL, instead performing that initialization + # by default + AC_CHECK_DECL([LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT], + [AC_DEFINE([HAVE_LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT],, + [Whether LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT is defined])],, + [#include ]) + + # Older versions of libwebsockets do not define special macros for SSL + # connection flags, instead relying on documented integer values + AC_CHECK_DECL([LCCSCF_USE_SSL], + [AC_DEFINE([HAVE_LCCSCF_USE_SSL],, + [Whether LCCSCF_USE_SSL is defined])],, + [#include ]) + fi AM_CONDITIONAL([ENABLE_WEBSOCKETS], diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index f314c597..9cb0b13b 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -268,9 +268,15 @@ void* guac_kubernetes_client_thread(void* data) { * do our own validation - libwebsockets does not validate properly if * IP addresses are used. */ if (settings->use_ssl) { +#ifdef HAVE_LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; +#endif +#ifdef HAVE_LCCSCF_USE_SSL connection_info.ssl_connection = LCCSCF_USE_SSL | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; +#else + connection_info.ssl_connection = 2; /* SSL + no hostname check */ +#endif } /* Create libwebsockets context */ From b48a1b3a5d15e6c95fa64222342033eb584dc0a3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 21:51:07 -0700 Subject: [PATCH 034/175] GUACAMOLE-623: Use libwebsockets' dummy callback only if defined. --- configure.ac | 8 ++++++++ src/protocols/kubernetes/kubernetes.c | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index bb23f62c..672d19eb 100644 --- a/configure.ac +++ b/configure.ac @@ -1224,6 +1224,14 @@ then [Whether LCCSCF_USE_SSL is defined])],, [#include ]) + # Older versions of libwebsockets do not define a dummy callback which + # must be invoked after the main event callback is invoked; the main event + # callback must instead manually return zero + AC_CHECK_DECL([lws_callback_http_dummy], + [AC_DEFINE([HAVE_LWS_CALLBACK_HTTP_DUMMY],, + [Whether lws_callback_http_dummy() is defined])],, + [#include ]) + fi AM_CONDITIONAL([ENABLE_WEBSOCKETS], diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 9cb0b13b..fb38d680 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -66,8 +66,13 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, guac_client* client = guac_kubernetes_lws_current_client; /* Do not handle any further events if connection is closing */ - if (client->state != GUAC_CLIENT_RUNNING) + if (client->state != GUAC_CLIENT_RUNNING) { +#ifdef HAVE_LWS_CALLBACK_HTTP_DUMMY return lws_callback_http_dummy(wsi, reason, user, in, length); +#else + return 0; +#endif + } switch (reason) { @@ -127,7 +132,11 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, } +#ifdef HAVE_LWS_CALLBACK_HTTP_DUMMY return lws_callback_http_dummy(wsi, reason, user, in, length); +#else + return 0; +#endif } From 7ee624844a2ef577bfd5af6c9559919e1ba77846 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 21:51:46 -0700 Subject: [PATCH 035/175] GUACAMOLE-623: Remove unnecessary initialization of pwsi. The pwsi member was previously used to ensure the lws structure was made available to invocations of the event callback early in the connection lifecycle such that the underlyin guac_client could always be retrieved. Since the migration to guac_kubernetes_lws_current_client, this is not necessary, and isn't supported in older versions of libwebsockets anyway. --- src/protocols/kubernetes/kubernetes.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index fb38d680..66fc27d4 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -268,7 +268,6 @@ void* guac_kubernetes_client_thread(void* data) { .origin = settings->hostname, .port = settings->port, .protocol = GUAC_KUBERNETES_LWS_PROTOCOL, - .pwsi = &kubernetes_client->wsi, .userdata = client }; From 44d3433ea92de6de7d127f93335c0a5be47c735c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 22:01:43 -0700 Subject: [PATCH 036/175] GUACAMOLE-623: Explicitly bypass certificate checks if requested. For older versions of libwebsockets, simply requesting that OpenSSL ignore the verification result is insufficient, as libwebsockets manually checks and confirms the verification result, producing an error in all but specific cases. --- src/protocols/kubernetes/ssl.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/protocols/kubernetes/ssl.c b/src/protocols/kubernetes/ssl.c index 6ebafc61..520ce8cb 100644 --- a/src/protocols/kubernetes/ssl.c +++ b/src/protocols/kubernetes/ssl.c @@ -110,6 +110,27 @@ static EVP_PKEY* guac_kubernetes_read_key(char* pem) { } +/** + * OpenSSL certificate verification callback which universally accepts all + * certificates without performing any verification at all. + * + * @param x509_ctx + * The current context of the certificate verification process. This + * parameter is ignored by this particular implementation of the callback. + * + * @param arg + * The arbitrary value passed to SSL_CTX_set_cert_verify_callback(). This + * parameter is ignored by this particular implementation of the callback. + * + * @return + * Strictly 0 if certificate verification fails, 1 if the certificate is + * verified. No other values are legal return values for this callback as + * documented by OpenSSL. + */ +static int guac_kubernetes_assume_cert_ok(X509_STORE_CTX* x509_ctx, void* arg) { + return 1; +} + void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) { guac_kubernetes_client* kubernetes_client = @@ -118,8 +139,11 @@ void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) { guac_kubernetes_settings* settings = kubernetes_client->settings; /* Bypass certificate checks if requested */ - if (settings->ignore_cert) - SSL_CTX_set_verify(context, SSL_VERIFY_NONE, NULL); + if (settings->ignore_cert) { + SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_cert_verify_callback(context, + guac_kubernetes_assume_cert_ok, NULL); + } /* Otherwise use the given CA certificate to validate (if any) */ else if (settings->ca_cert != NULL) { From 9c593bde89891522324fe84034e4a9bd415aae2c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 22:30:08 -0700 Subject: [PATCH 037/175] GUACAMOLE-623: Kill connection if libwebsockets is destroying the underlying WebSocket. Older versions of libwebsockets will not necessarily invoke close events under all circumstances, and will instead sometimes summarily destroy the WebSocket. Thankfully there is another event for that, and newer versions of libwebsockets continue to define that event. We can hook into both to handle disconnect. --- src/protocols/kubernetes/kubernetes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 66fc27d4..e115fc57 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -120,6 +120,7 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, #endif /* Connection closed */ + case LWS_CALLBACK_WSI_DESTROY: case LWS_CALLBACK_CLOSED: guac_client_stop(client); guac_client_log(client, GUAC_LOG_DEBUG, "WebSocket connection to " From 7374b29364a1d4e9d095843b014ce0ea19add0b9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 22:05:46 -0700 Subject: [PATCH 038/175] GUACAMOLE-623: Start terminal once Kubernetes connection is ready (necessary since merge of GUACAMOLE-622). --- src/protocols/kubernetes/kubernetes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index e115fc57..7c22c420 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -64,6 +64,8 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, void* in, size_t length) { guac_client* client = guac_kubernetes_lws_current_client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; /* Do not handle any further events if connection is closing */ if (client->state != GUAC_CLIENT_RUNNING) { @@ -94,6 +96,9 @@ static int guac_kubernetes_lws_callback(struct lws* wsi, guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection successful."); + /* Allow terminal to render */ + guac_terminal_start(kubernetes_client->term); + /* Schedule check for pending messages in case messages were added * to the outbound message buffer prior to the connection being * fully established */ From 64b1572d13f573b310c5b60d8e83f760a473cb54 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 26 Sep 2018 19:36:55 -0700 Subject: [PATCH 039/175] GUACAMOLE-623: Include dependences of Kubernetes support in Docker image build. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 4d463a38..d903c4a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,6 +49,7 @@ ARG BUILD_DEPENDENCIES=" \ libtelnet-dev \ libtool \ libvncserver-dev \ + libwebsockets-dev \ libwebp-dev \ make" From d851f10a48500a348a134fab6f1a637bade12109 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 8 Feb 2018 16:05:33 -0800 Subject: [PATCH 040/175] GUACAMOLE-353: Clarify applicability of ASF header when transcluded into generated build files. --- Makefile.am | 6 ++++++ src/common-ssh/Makefile.am | 6 ++++++ src/common/Makefile.am | 6 ++++++ src/guacd/Makefile.am | 6 ++++++ src/guacenc/Makefile.am | 6 ++++++ src/guaclog/Makefile.am | 6 ++++++ src/libguac/Makefile.am | 6 ++++++ src/protocols/kubernetes/Makefile.am | 6 ++++++ src/protocols/rdp/Makefile.am | 6 ++++++ src/protocols/ssh/Makefile.am | 6 ++++++ src/protocols/telnet/Makefile.am | 6 ++++++ src/protocols/vnc/Makefile.am | 6 ++++++ src/pulse/Makefile.am | 6 ++++++ src/terminal/Makefile.am | 6 ++++++ tests/Makefile.am | 6 ++++++ 15 files changed, 90 insertions(+) diff --git a/Makefile.am b/Makefile.am index 63bd8e08..6926474b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# ACLOCAL_AMFLAGS = -I m4 diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index b839ab00..81167238 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 32d12836..06190103 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/guacd/Makefile.am b/src/guacd/Makefile.am index 4dcd3d22..356f72f2 100644 --- a/src/guacd/Makefile.am +++ b/src/guacd/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index a505ff5b..8debd632 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign diff --git a/src/guaclog/Makefile.am b/src/guaclog/Makefile.am index 6acced30..af5c5a76 100644 --- a/src/guaclog/Makefile.am +++ b/src/guaclog/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 5123121d..d541df58 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index 56db4d64..b100c176 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index cffabb2c..670359fc 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index 87d4a728..d60cf002 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index a44118de..bec0e955 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index 0c5ea478..fcba33f1 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/pulse/Makefile.am b/src/pulse/Makefile.am index b7921245..3cb2d44c 100644 --- a/src/pulse/Makefile.am +++ b/src/pulse/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 1a609324..9fce9dc5 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 diff --git a/tests/Makefile.am b/tests/Makefile.am index 7c7e07bc..fc1302e5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -16,6 +16,12 @@ # specific language governing permissions and limitations # under the License. # +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 From 34c088882b2678a353a3ea101227fce9db42babf Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 16 Oct 2018 09:27:45 -0400 Subject: [PATCH 041/175] GUACAMOLE-527: Correct issue with order of VNC SFTP settings. --- src/protocols/vnc/settings.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 587864a4..8f65cfb2 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -197,6 +197,11 @@ enum VNC_ARGS_IDX { */ IDX_SFTP_HOSTNAME, + /** + * The public SSH host key to identify the SFTP server. + */ + IDX_SFTP_HOST_KEY, + /** * The port of the SSH server to connect to for SFTP. If blank, the default * SSH port of "22" will be used. @@ -209,11 +214,6 @@ enum VNC_ARGS_IDX { */ IDX_SFTP_USERNAME, - /** - * The public SSH host key to identify the SFTP server. - */ - IDX_SFTP_HOST_KEY, - /** * The password to provide when authenticating with the SSH server for * SFTP (if not using a private key). From 6f9f2189f278e75e0a3e89851827f317757ee16e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 22 Sep 2018 16:07:42 -0700 Subject: [PATCH 042/175] GUACAMOLE-630: Separate color scheme parsing into own files. --- src/terminal/Makefile.am | 2 + src/terminal/color-scheme.c | 251 +++++++++++++++++++++++++++ src/terminal/terminal.c | 243 +------------------------- src/terminal/terminal/color-scheme.h | 95 ++++++++++ src/terminal/terminal/terminal.h | 35 ---- 5 files changed, 349 insertions(+), 277 deletions(-) create mode 100644 src/terminal/color-scheme.c create mode 100644 src/terminal/terminal/color-scheme.h diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 9fce9dc5..a886479e 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -32,6 +32,7 @@ noinst_HEADERS = \ terminal/buffer.h \ terminal/char_mappings.h \ terminal/common.h \ + terminal/color-scheme.h \ terminal/display.h \ terminal/named-colors.h \ terminal/palette.h \ @@ -46,6 +47,7 @@ noinst_HEADERS = \ libguac_terminal_la_SOURCES = \ buffer.c \ char_mappings.c \ + color-scheme.c \ common.c \ display.c \ named-colors.c \ diff --git a/src/terminal/color-scheme.c b/src/terminal/color-scheme.c new file mode 100644 index 00000000..2799aac9 --- /dev/null +++ b/src/terminal/color-scheme.c @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "terminal/color-scheme.h" +#include "terminal/palette.h" +#include "terminal/xparsecolor.h" + +#include +#include +#include +#include + +#include + +/** + * Compare a non-null-terminated string to a null-terminated literal, in the + * same manner as strcmp(). + * + * @param str_start + * Start of the non-null-terminated string. + * + * @param str_end + * End of the non-null-terminated string, after the last character. + * + * @param literal + * The null-terminated literal to compare against. + * + * @return + * Zero if the two strings are equal and non-zero otherwise. + */ +static int guac_terminal_color_scheme_compare_token(const char* str_start, + const char* str_end, const char* literal) { + + const int result = strncmp(literal, str_start, str_end - str_start); + if (result != 0) + return result; + + /* At this point, literal is same length or longer than + * | str_end - str_start |, so if the two are equal, literal should + * have its null-terminator at | str_end - str_start |. */ + return (int) (unsigned char) literal[str_end - str_start]; +} + +/** + * Strip the leading and trailing spaces of a bounded string. + * + * @param[in,out] str_start + * Address of a pointer to the start of the string. On return, the pointer + * is advanced to after any leading spaces. + * + * @param[in,out] str_end + * Address of a pointer to the end of the string, after the last character. + * On return, the pointer is moved back to before any trailing spaces. + */ +static void guac_terminal_color_scheme_strip_spaces(const char** str_start, + const char** str_end) { + + /* Strip leading spaces. */ + while (*str_start < *str_end && isspace(**str_start)) + (*str_start)++; + + /* Strip trailing spaces. */ + while (*str_end > *str_start && isspace(*(*str_end - 1))) + (*str_end)--; +} + +/** + * Parse the name part of the name-value pair within the color-scheme + * configuration. + * + * @param client + * The client that the terminal is connected to. + * + * @param name_start + * Start of the name string. + * + * @param name_end + * End of the name string, after the last character. + * + * @param foreground + * Pointer to the foreground color. + * + * @param background + * Pointer to the background color. + * + * @param palette + * Pointer to the palette array. + * + * @param[out] target + * On return, pointer to the color struct that corresponds to the name. + * + * @return + * Zero if successful or non-zero otherwise. + */ +static int guac_terminal_parse_color_scheme_name(guac_client* client, + const char* name_start, const char* name_end, + guac_terminal_color* foreground, guac_terminal_color* background, + guac_terminal_color (*palette)[256], + guac_terminal_color** target) { + + guac_terminal_color_scheme_strip_spaces(&name_start, &name_end); + + if (!guac_terminal_color_scheme_compare_token( + name_start, name_end, GUAC_TERMINAL_SCHEME_FOREGROUND)) { + *target = foreground; + return 0; + } + + if (!guac_terminal_color_scheme_compare_token( + name_start, name_end, GUAC_TERMINAL_SCHEME_BACKGROUND)) { + *target = background; + return 0; + } + + /* Parse color value. */ + int index = -1; + if (sscanf(name_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && + index >= 0 && index <= 255) { + *target = &(*palette)[index]; + return 0; + } + + guac_client_log(client, GUAC_LOG_WARNING, + "Unknown color name: \"%.*s\".", + name_end - name_start, name_start); + return 1; +} + +/** + * Parse the value part of the name-value pair within the color-scheme + * configuration. + * + * @param client + * The client that the terminal is connected to. + * + * @param value_start + * Start of the value string. + * + * @param value_end + * End of the value string, after the last character. + * + * @param palette + * The current color palette. + * + * @param[out] target + * On return, the parsed color. + * + * @return + * Zero if successful or non-zero otherwise. + */ +static int guac_terminal_parse_color_scheme_value(guac_client* client, + const char* value_start, const char* value_end, + const guac_terminal_color (*palette)[256], + guac_terminal_color* target) { + + guac_terminal_color_scheme_strip_spaces(&value_start, &value_end); + + /* Parse color value. */ + int index = -1; + if (sscanf(value_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && + index >= 0 && index <= 255) { + *target = (*palette)[index]; + return 0; + } + + /* Parse X11 value. */ + if (!guac_terminal_xparsecolor(value_start, target)) + return 0; + + guac_client_log(client, GUAC_LOG_WARNING, + "Invalid color value: \"%.*s\".", + value_end - value_start, value_start); + return 1; +} + +void guac_terminal_parse_color_scheme(guac_client* client, + const char* color_scheme, guac_terminal_color* foreground, + guac_terminal_color* background, + guac_terminal_color (*palette)[256]) { + + /* Set default gray-black color scheme and initial palette. */ + *foreground = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_GRAY]; + *background = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_BLACK]; + memcpy(palette, GUAC_TERMINAL_INITIAL_PALETTE, + sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); + + /* Current char being parsed, or NULL if at end of parsing. */ + const char* cursor = color_scheme; + + while (cursor) { + /* Start of the current "name: value" pair. */ + const char* pair_start = cursor; + + /* End of the current name-value pair. */ + const char* pair_end = strchr(pair_start, ';'); + if (pair_end) { + cursor = pair_end + 1; + } + else { + pair_end = pair_start + strlen(pair_start); + cursor = NULL; + } + + guac_terminal_color_scheme_strip_spaces(&pair_start, &pair_end); + if (pair_start >= pair_end) + /* Allow empty pairs, which happens, e.g., when the configuration + * string ends in a semi-colon. */ + continue; + + /* End of the name part of the pair. */ + const char* name_end = memchr(pair_start, ':', pair_end - pair_start); + if (name_end == NULL) { + guac_client_log(client, GUAC_LOG_WARNING, + "Expecting colon: \"%.*s\".", + pair_end - pair_start, pair_start); + return; + } + + /* The color that the name corresponds to. */ + guac_terminal_color* color_target = NULL; + + if (guac_terminal_parse_color_scheme_name( + client, pair_start, name_end, foreground, background, + palette, &color_target)) + return; /* Parsing failed. */ + + if (guac_terminal_parse_color_scheme_value( + client, name_end + 1, pair_end, + (const guac_terminal_color(*)[256]) palette, color_target)) + return; /* Parsing failed. */ + } +} + diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 95e945d8..a99bfb05 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -22,6 +22,7 @@ #include "common/clipboard.h" #include "common/cursor.h" #include "terminal/buffer.h" +#include "terminal/color-scheme.h" #include "terminal/common.h" #include "terminal/display.h" #include "terminal/palette.h" @@ -30,7 +31,6 @@ #include "terminal/terminal_handlers.h" #include "terminal/types.h" #include "terminal/typescript.h" -#include "terminal/xparsecolor.h" #include #include @@ -305,247 +305,6 @@ void* guac_terminal_thread(void* data) { } -/** - * Compare a non-null-terminated string to a null-terminated literal, in the - * same manner as strcmp(). - * - * @param str_start - * Start of the non-null-terminated string. - * - * @param str_end - * End of the non-null-terminated string, after the last character. - * - * @param literal - * The null-terminated literal to compare against. - * - * @return - * Zero if the two strings are equal and non-zero otherwise. - */ -static int guac_terminal_color_scheme_compare_token(const char* str_start, - const char* str_end, const char* literal) { - - const int result = strncmp(literal, str_start, str_end - str_start); - if (result != 0) - return result; - - /* At this point, literal is same length or longer than - * | str_end - str_start |, so if the two are equal, literal should - * have its null-terminator at | str_end - str_start |. */ - return (int) (unsigned char) literal[str_end - str_start]; -} - -/** - * Strip the leading and trailing spaces of a bounded string. - * - * @param[in,out] str_start - * Address of a pointer to the start of the string. On return, the pointer - * is advanced to after any leading spaces. - * - * @param[in,out] str_end - * Address of a pointer to the end of the string, after the last character. - * On return, the pointer is moved back to before any trailing spaces. - */ -static void guac_terminal_color_scheme_strip_spaces(const char** str_start, - const char** str_end) { - - /* Strip leading spaces. */ - while (*str_start < *str_end && isspace(**str_start)) - (*str_start)++; - - /* Strip trailing spaces. */ - while (*str_end > *str_start && isspace(*(*str_end - 1))) - (*str_end)--; -} - -/** - * Parse the name part of the name-value pair within the color-scheme - * configuration. - * - * @param client - * The client that the terminal is connected to. - * - * @param name_start - * Start of the name string. - * - * @param name_end - * End of the name string, after the last character. - * - * @param foreground - * Pointer to the foreground color. - * - * @param background - * Pointer to the background color. - * - * @param palette - * Pointer to the palette array. - * - * @param[out] target - * On return, pointer to the color struct that corresponds to the name. - * - * @return - * Zero if successful or non-zero otherwise. - */ -static int guac_terminal_parse_color_scheme_name(guac_client* client, - const char* name_start, const char* name_end, - guac_terminal_color* foreground, guac_terminal_color* background, - guac_terminal_color (*palette)[256], - guac_terminal_color** target) { - - guac_terminal_color_scheme_strip_spaces(&name_start, &name_end); - - if (!guac_terminal_color_scheme_compare_token( - name_start, name_end, GUAC_TERMINAL_SCHEME_FOREGROUND)) { - *target = foreground; - return 0; - } - - if (!guac_terminal_color_scheme_compare_token( - name_start, name_end, GUAC_TERMINAL_SCHEME_BACKGROUND)) { - *target = background; - return 0; - } - - /* Parse color value. */ - int index = -1; - if (sscanf(name_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && - index >= 0 && index <= 255) { - *target = &(*palette)[index]; - return 0; - } - - guac_client_log(client, GUAC_LOG_WARNING, - "Unknown color name: \"%.*s\".", - name_end - name_start, name_start); - return 1; -} - -/** - * Parse the value part of the name-value pair within the color-scheme - * configuration. - * - * @param client - * The client that the terminal is connected to. - * - * @param value_start - * Start of the value string. - * - * @param value_end - * End of the value string, after the last character. - * - * @param palette - * The current color palette. - * - * @param[out] target - * On return, the parsed color. - * - * @return - * Zero if successful or non-zero otherwise. - */ -static int guac_terminal_parse_color_scheme_value(guac_client* client, - const char* value_start, const char* value_end, - const guac_terminal_color (*palette)[256], - guac_terminal_color* target) { - - guac_terminal_color_scheme_strip_spaces(&value_start, &value_end); - - /* Parse color value. */ - int index = -1; - if (sscanf(value_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && - index >= 0 && index <= 255) { - *target = (*palette)[index]; - return 0; - } - - /* Parse X11 value. */ - if (!guac_terminal_xparsecolor(value_start, target)) - return 0; - - guac_client_log(client, GUAC_LOG_WARNING, - "Invalid color value: \"%.*s\".", - value_end - value_start, value_start); - return 1; -} - -/** - * Parse a color-scheme configuration string, and return specified - * foreground/background colors and color palette. - * - * @param client - * The client that the terminal is connected to. - * - * @param color_scheme - * A semicolon-separated list of name-value pairs, i.e. - * ": [; : [; ...]]". - * For example, "color2: rgb:cc/33/22; background: color5". - * - * @param[out] foreground - * Parsed foreground color. - * - * @param[out] background - * Parsed background color. - * - * @param[in,out] palette - * Parsed color palette. The caller is responsible for allocating a mutable - * array on entry. On return, the array contains the parsed palette. - */ -static void guac_terminal_parse_color_scheme(guac_client* client, - const char* color_scheme, guac_terminal_color* foreground, - guac_terminal_color* background, - guac_terminal_color (*palette)[256]) { - - /* Set default gray-black color scheme and initial palette. */ - *foreground = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_GRAY]; - *background = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_BLACK]; - memcpy(palette, GUAC_TERMINAL_INITIAL_PALETTE, - sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); - - /* Current char being parsed, or NULL if at end of parsing. */ - const char* cursor = color_scheme; - - while (cursor) { - /* Start of the current "name: value" pair. */ - const char* pair_start = cursor; - - /* End of the current name-value pair. */ - const char* pair_end = strchr(pair_start, ';'); - if (pair_end) { - cursor = pair_end + 1; - } - else { - pair_end = pair_start + strlen(pair_start); - cursor = NULL; - } - - guac_terminal_color_scheme_strip_spaces(&pair_start, &pair_end); - if (pair_start >= pair_end) - /* Allow empty pairs, which happens, e.g., when the configuration - * string ends in a semi-colon. */ - continue; - - /* End of the name part of the pair. */ - const char* name_end = memchr(pair_start, ':', pair_end - pair_start); - if (name_end == NULL) { - guac_client_log(client, GUAC_LOG_WARNING, - "Expecting colon: \"%.*s\".", - pair_end - pair_start, pair_start); - return; - } - - /* The color that the name corresponds to. */ - guac_terminal_color* color_target = NULL; - - if (guac_terminal_parse_color_scheme_name( - client, pair_start, name_end, foreground, background, - palette, &color_target)) - return; /* Parsing failed. */ - - if (guac_terminal_parse_color_scheme_value( - client, name_end + 1, pair_end, - (const guac_terminal_color(*)[256]) palette, color_target)) - return; /* Parsing failed. */ - } -} - guac_terminal* guac_terminal_create(guac_client* client, guac_common_clipboard* clipboard, int max_scrollback, const char* font_name, int font_size, int dpi, diff --git a/src/terminal/terminal/color-scheme.h b/src/terminal/terminal/color-scheme.h new file mode 100644 index 00000000..962d820d --- /dev/null +++ b/src/terminal/terminal/color-scheme.h @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_TERMINAL_COLOR_SCHEME_H +#define GUAC_TERMINAL_COLOR_SCHEME_H + +#include "config.h" + +#include "terminal/palette.h" + +#include + +#include +#include + +/** + * The name of the color scheme having black foreground and white background. + */ +#define GUAC_TERMINAL_SCHEME_BLACK_WHITE "black-white" + +/** + * The name of the color scheme having gray foreground and black background. + */ +#define GUAC_TERMINAL_SCHEME_GRAY_BLACK "gray-black" + +/** + * The name of the color scheme having green foreground and black background. + */ +#define GUAC_TERMINAL_SCHEME_GREEN_BLACK "green-black" + +/** + * The name of the color scheme having white foreground and black background. + */ +#define GUAC_TERMINAL_SCHEME_WHITE_BLACK "white-black" + +/** + * Color name representing the foreground color. + */ +#define GUAC_TERMINAL_SCHEME_FOREGROUND "foreground" + +/** + * Color name representing the background color. + */ +#define GUAC_TERMINAL_SCHEME_BACKGROUND "background" + +/** + * Color name representing a numbered color. + */ +#define GUAC_TERMINAL_SCHEME_NUMBERED "color" + +/** + * Parse a color-scheme configuration string, and return specified + * foreground/background colors and color palette. + * + * @param client + * The client that the terminal is connected to. + * + * @param color_scheme + * A semicolon-separated list of name-value pairs, i.e. + * ": [; : [; ...]]". + * For example, "color2: rgb:cc/33/22; background: color5". + * + * @param[out] foreground + * Parsed foreground color. + * + * @param[out] background + * Parsed background color. + * + * @param[in,out] palette + * Parsed color palette. The caller is responsible for allocating a mutable + * array on entry. On return, the array contains the parsed palette. + */ +void guac_terminal_parse_color_scheme(guac_client* client, + const char* color_scheme, guac_terminal_color* foreground, + guac_terminal_color* background, + guac_terminal_color (*palette)[256]); + +#endif + diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 08094baa..aee7742c 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -69,41 +69,6 @@ */ #define GUAC_TERMINAL_WHEEL_SCROLL_AMOUNT 3 -/** - * The name of the color scheme having black foreground and white background. - */ -#define GUAC_TERMINAL_SCHEME_BLACK_WHITE "black-white" - -/** - * The name of the color scheme having gray foreground and black background. - */ -#define GUAC_TERMINAL_SCHEME_GRAY_BLACK "gray-black" - -/** - * The name of the color scheme having green foreground and black background. - */ -#define GUAC_TERMINAL_SCHEME_GREEN_BLACK "green-black" - -/** - * The name of the color scheme having white foreground and black background. - */ -#define GUAC_TERMINAL_SCHEME_WHITE_BLACK "white-black" - -/** - * Color name representing the foreground color. - */ -#define GUAC_TERMINAL_SCHEME_FOREGROUND "foreground" - -/** - * Color name representing the background color. - */ -#define GUAC_TERMINAL_SCHEME_BACKGROUND "background" - -/** - * Color name representing a numbered color. - */ -#define GUAC_TERMINAL_SCHEME_NUMBERED "color" - /** * Flag which specifies that terminal output should be sent to both the current * pipe stream and the user's display. By default, terminal output will be sent From 2f16eadb358362d1925da343e530ab73417d4f55 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 23 Sep 2018 00:24:19 -0700 Subject: [PATCH 043/175] GUACAMOLE-630: Allow color scheme to be changed from webapp via argv streams. --- src/protocols/ssh/Makefile.am | 2 + src/protocols/ssh/argv.c | 128 +++++++++++++++++++++++++++++++ src/protocols/ssh/argv.h | 35 +++++++++ src/protocols/ssh/user.c | 4 + src/protocols/telnet/Makefile.am | 2 + src/protocols/telnet/argv.c | 128 +++++++++++++++++++++++++++++++ src/protocols/telnet/argv.h | 35 +++++++++ src/protocols/telnet/user.c | 4 + src/terminal/display.c | 4 +- src/terminal/terminal.c | 29 ++++++- src/terminal/terminal/display.h | 4 +- src/terminal/terminal/terminal.h | 15 ++++ 12 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 src/protocols/ssh/argv.c create mode 100644 src/protocols/ssh/argv.h create mode 100644 src/protocols/telnet/argv.c create mode 100644 src/protocols/telnet/argv.h diff --git a/src/protocols/ssh/Makefile.am b/src/protocols/ssh/Makefile.am index d60cf002..7bb1e349 100644 --- a/src/protocols/ssh/Makefile.am +++ b/src/protocols/ssh/Makefile.am @@ -29,6 +29,7 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-ssh.la libguac_client_ssh_la_SOURCES = \ + argv.c \ client.c \ clipboard.c \ input.c \ @@ -40,6 +41,7 @@ libguac_client_ssh_la_SOURCES = \ user.c noinst_HEADERS = \ + argv.h \ client.h \ clipboard.h \ input.h \ diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c new file mode 100644 index 00000000..85dca943 --- /dev/null +++ b/src/protocols/ssh/argv.c @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "argv.h" +#include "ssh.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include +#include + +/** + * The value or current status of a connection parameter received over an + * "argv" stream. + */ +typedef struct guac_ssh_argv { + + /** + * Buffer space for containing the received argument value. + */ + char buffer[16384]; + + /** + * The number of bytes received so far. + */ + int length; + +} guac_ssh_argv; + +/** + * Handler for "blob" instructions which appends the data from received blobs + * to the end of the in-progress argument value buffer. + * + * @see guac_user_blob_handler + */ +static int guac_ssh_argv_blob_handler(guac_user* user, + guac_stream* stream, void* data, int length) { + + guac_ssh_argv* argv = (guac_ssh_argv*) stream->data; + + /* Calculate buffer size remaining, including space for null terminator, + * adjusting received length accordingly */ + int remaining = sizeof(argv->buffer) - argv->length - 1; + if (length > remaining) + length = remaining; + + /* Append received data to end of buffer */ + memcpy(argv->buffer + argv->length, data, length); + argv->length += length; + + return 0; + +} + +/** + * Handler for "end" instructions which applies the changes specified by the + * argument value buffer associated with the stream. + * + * @see guac_user_end_handler + */ +static int guac_ssh_argv_end_handler(guac_user* user, + guac_stream* stream) { + + guac_client* client = user->client; + guac_ssh_client* telnet_client = (guac_ssh_client*) client->data; + guac_terminal* terminal = telnet_client->term; + + /* Append null terminator to value */ + guac_ssh_argv* argv = (guac_ssh_argv*) stream->data; + argv->buffer[argv->length] = '\0'; + + /* Update color scheme */ + guac_terminal_apply_color_scheme(terminal, argv->buffer); + free(argv); + return 0; + +} + +int guac_ssh_argv_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + /* Allow users to update the color scheme */ + if (strcmp(name, "color-scheme") == 0) { + + guac_ssh_argv* argv = malloc(sizeof(guac_ssh_argv)); + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_ssh_argv_blob_handler; + stream->end_handler = guac_ssh_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for color " + "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + + } + + /* No other connection parameters may be updated */ + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h new file mode 100644 index 00000000..27b08cbf --- /dev/null +++ b/src/protocols/ssh/argv.h @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_SSH_ARGV_H +#define GUAC_SSH_ARGV_H + +#include "config.h" + +#include + +/** + * Handles an incoming stream from a Guacamole "argv" instruction, updating the + * given connection parameter if that parameter is allowed to be updated. + */ +guac_user_argv_handler guac_ssh_argv_handler; + +#endif + diff --git a/src/protocols/ssh/user.c b/src/protocols/ssh/user.c index e4dda40e..97ed87c6 100644 --- a/src/protocols/ssh/user.c +++ b/src/protocols/ssh/user.c @@ -19,6 +19,7 @@ #include "config.h" +#include "argv.h" #include "clipboard.h" #include "common/display.h" #include "input.h" @@ -87,6 +88,9 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) { /* STDIN redirection */ user->pipe_handler = guac_ssh_pipe_handler; + /* Updates to connection parameters */ + user->argv_handler = guac_ssh_argv_handler; + /* Display size change events */ user->size_handler = guac_ssh_user_size_handler; diff --git a/src/protocols/telnet/Makefile.am b/src/protocols/telnet/Makefile.am index bec0e955..d0264f67 100644 --- a/src/protocols/telnet/Makefile.am +++ b/src/protocols/telnet/Makefile.am @@ -29,6 +29,7 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-telnet.la libguac_client_telnet_la_SOURCES = \ + argv.c \ client.c \ clipboard.c \ input.c \ @@ -38,6 +39,7 @@ libguac_client_telnet_la_SOURCES = \ user.c noinst_HEADERS = \ + argv.h \ client.h \ clipboard.h \ input.h \ diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c new file mode 100644 index 00000000..da405aa9 --- /dev/null +++ b/src/protocols/telnet/argv.c @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "argv.h" +#include "telnet.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include +#include + +/** + * The value or current status of a connection parameter received over an + * "argv" stream. + */ +typedef struct guac_telnet_argv { + + /** + * Buffer space for containing the received argument value. + */ + char buffer[16384]; + + /** + * The number of bytes received so far. + */ + int length; + +} guac_telnet_argv; + +/** + * Handler for "blob" instructions which appends the data from received blobs + * to the end of the in-progress argument value buffer. + * + * @see guac_user_blob_handler + */ +static int guac_telnet_argv_blob_handler(guac_user* user, + guac_stream* stream, void* data, int length) { + + guac_telnet_argv* argv = (guac_telnet_argv*) stream->data; + + /* Calculate buffer size remaining, including space for null terminator, + * adjusting received length accordingly */ + int remaining = sizeof(argv->buffer) - argv->length - 1; + if (length > remaining) + length = remaining; + + /* Append received data to end of buffer */ + memcpy(argv->buffer + argv->length, data, length); + argv->length += length; + + return 0; + +} + +/** + * Handler for "end" instructions which applies the changes specified by the + * argument value buffer associated with the stream. + * + * @see guac_user_end_handler + */ +static int guac_telnet_argv_end_handler(guac_user* user, + guac_stream* stream) { + + guac_client* client = user->client; + guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; + guac_terminal* terminal = telnet_client->term; + + /* Append null terminator to value */ + guac_telnet_argv* argv = (guac_telnet_argv*) stream->data; + argv->buffer[argv->length] = '\0'; + + /* Update color scheme */ + guac_terminal_apply_color_scheme(terminal, argv->buffer); + free(argv); + return 0; + +} + +int guac_telnet_argv_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + /* Allow users to update the color scheme */ + if (strcmp(name, "color-scheme") == 0) { + + guac_telnet_argv* argv = malloc(sizeof(guac_telnet_argv)); + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_telnet_argv_blob_handler; + stream->end_handler = guac_telnet_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for color " + "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + + } + + /* No other connection parameters may be updated */ + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h new file mode 100644 index 00000000..b1567ea8 --- /dev/null +++ b/src/protocols/telnet/argv.h @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_TELNET_ARGV_H +#define GUAC_TELNET_ARGV_H + +#include "config.h" + +#include + +/** + * Handles an incoming stream from a Guacamole "argv" instruction, updating the + * given connection parameter if that parameter is allowed to be updated. + */ +guac_user_argv_handler guac_telnet_argv_handler; + +#endif + diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c index 2e34f780..44ef9b5e 100644 --- a/src/protocols/telnet/user.c +++ b/src/protocols/telnet/user.c @@ -19,6 +19,7 @@ #include "config.h" +#include "argv.h" #include "clipboard.h" #include "input.h" #include "pipe.h" @@ -86,6 +87,9 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { /* STDIN redirection */ user->pipe_handler = guac_telnet_pipe_handler; + /* Updates to connection parameters */ + user->argv_handler = guac_telnet_argv_handler; + /* Display size change events */ user->size_handler = guac_telnet_user_size_handler; diff --git a/src/terminal/display.c b/src/terminal/display.c index c4c74713..480e8398 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -199,7 +199,7 @@ int __guac_terminal_set(guac_terminal_display* display, int row, int col, int co guac_terminal_display* guac_terminal_display_alloc(guac_client* client, const char* font_name, int font_size, int dpi, guac_terminal_color* foreground, guac_terminal_color* background, - const guac_terminal_color (*palette)[256]) { + guac_terminal_color (*palette)[256]) { PangoFontMap* font_map; PangoFont* font; @@ -271,7 +271,7 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, void guac_terminal_display_free(guac_terminal_display* display) { /* Free default palette. */ - free((void*) display->default_palette); + free(display->default_palette); /* Free operations buffers */ free(display->operations); diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index a99bfb05..c12df4b9 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -384,7 +384,7 @@ guac_terminal* guac_terminal_create(guac_client* client, font_name, font_size, dpi, &default_char.attributes.foreground, &default_char.attributes.background, - (const guac_terminal_color(*)[256]) default_palette); + (guac_terminal_color(*)[256]) default_palette); /* Fail if display init failed */ if (term->display == NULL) { @@ -1940,3 +1940,30 @@ void guac_terminal_dup(guac_terminal* term, guac_user* user, } +void guac_terminal_apply_color_scheme(guac_terminal* terminal, + const char* color_scheme) { + + guac_client* client = terminal->client; + guac_terminal_char* default_char = &terminal->default_char; + guac_terminal_display* display = terminal->display; + + /* Reinitialize default terminal colors with values from color scheme */ + guac_terminal_parse_color_scheme(client, color_scheme, + &default_char->attributes.foreground, + &default_char->attributes.background, + display->default_palette); + + /* Reinitialize default attributes of buffer and display */ + terminal->buffer->default_character = *default_char; + display->default_foreground = default_char->attributes.foreground; + display->default_background = default_char->attributes.background; + + /* Redraw background with new color */ + guac_terminal_repaint_default_layer(terminal, client->socket); + + /* Force reset of terminal state */ + guac_terminal_reset(terminal); + guac_terminal_notify(terminal); + +} + diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index e54003bb..b1274235 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -142,7 +142,7 @@ typedef struct guac_terminal_display { * The default palette. Use GUAC_TERMINAL_INITIAL_PALETTE if null. * Must free on destruction if not null. */ - const guac_terminal_color (*default_palette)[256]; + guac_terminal_color (*default_palette)[256]; /** * Default foreground color for all glyphs. @@ -215,7 +215,7 @@ typedef struct guac_terminal_display { guac_terminal_display* guac_terminal_display_alloc(guac_client* client, const char* font_name, int font_size, int dpi, guac_terminal_color* foreground, guac_terminal_color* background, - const guac_terminal_color (*palette)[256]); + guac_terminal_color (*palette)[256]); /** * Frees the given display. diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index aee7742c..6de984bf 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -1072,5 +1072,20 @@ int guac_terminal_create_typescript(guac_terminal* term, const char* path, */ int guac_terminal_available_scroll(guac_terminal* term); +/** + * Immediately applies the given color scheme to the given terminal, overriding + * the color scheme provided when the terminal was created. Applying the color + * scheme implicitly clears the display and resets the terminal state. Valid + * color schemes are those accepted by guac_terminal_parse_color_scheme(). + * + * @param terminal + * The terminal to apply the color scheme to. + * + * @param color_scheme + * The color scheme to apply. + */ +void guac_terminal_apply_color_scheme(guac_terminal* terminal, + const char* color_scheme); + #endif From dcab540839f46aa3dbe919929e4b999c713bee5d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 24 Sep 2018 01:25:30 -0700 Subject: [PATCH 044/175] GUACAMOLE-630: Persist semantics of default foreground/background with dedicated palette pseudo-indexes. --- src/terminal/color-scheme.c | 5 +++++ src/terminal/display.c | 33 +++++++++++++++++++++++++++------ src/terminal/palette.c | 4 ++++ src/terminal/terminal/palette.h | 14 ++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/terminal/color-scheme.c b/src/terminal/color-scheme.c index 2799aac9..f4300196 100644 --- a/src/terminal/color-scheme.c +++ b/src/terminal/color-scheme.c @@ -247,5 +247,10 @@ void guac_terminal_parse_color_scheme(guac_client* client, (const guac_terminal_color(*)[256]) palette, color_target)) return; /* Parsing failed. */ } + + /* Persist pseudo-index for foreground/background colors */ + foreground->palette_index = GUAC_TERMINAL_COLOR_FOREGROUND; + background->palette_index = GUAC_TERMINAL_COLOR_BACKGROUND; + } diff --git a/src/terminal/display.c b/src/terminal/display.c index 480e8398..1c803b41 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -78,7 +78,12 @@ int __guac_terminal_set_colors(guac_terminal_display* display, } display->glyph_foreground = *foreground; + guac_terminal_display_lookup_color(display, + foreground->palette_index, &display->glyph_foreground); + display->glyph_background = *background; + guac_terminal_display_lookup_color(display, + background->palette_index, &display->glyph_background); /* Modify color if half-bright (low intensity) */ if (attributes->half_bright && !attributes->bold) { @@ -315,6 +320,18 @@ int guac_terminal_display_assign_color(guac_terminal_display* display, int guac_terminal_display_lookup_color(guac_terminal_display* display, int index, guac_terminal_color* color) { + /* Use default foreground if foreground pseudo-index is given */ + if (index == GUAC_TERMINAL_COLOR_FOREGROUND) { + *color = display->default_foreground; + return 0; + } + + /* Use default background if background pseudo-index is given */ + if (index == GUAC_TERMINAL_COLOR_BACKGROUND) { + *color = display->default_background; + return 0; + } + /* Lookup fails if out-of-bounds */ if (index < 0 || index > 255) return 1; @@ -658,11 +675,15 @@ void __guac_terminal_display_flush_clear(guac_terminal_display* display) { int rect_width, rect_height; /* Color of the rectangle to draw */ - const guac_terminal_color* color; + guac_terminal_color color; if (current->character.attributes.reverse != current->character.attributes.cursor) - color = ¤t->character.attributes.foreground; + color = current->character.attributes.foreground; else - color = ¤t->character.attributes.background; + color = current->character.attributes.background; + + /* Rely only on palette index if defined */ + guac_terminal_display_lookup_color(display, + color.palette_index, &color); /* Current row within a subrect */ guac_terminal_operation* rect_current_row; @@ -685,7 +706,7 @@ void __guac_terminal_display_flush_clear(guac_terminal_display* display) { /* If not identical operation, stop */ if (rect_current->type != GUAC_CHAR_SET || guac_terminal_has_glyph(rect_current->character.value) - || guac_terminal_colorcmp(joining_color, color) != 0) + || guac_terminal_colorcmp(joining_color, &color) != 0) break; /* Next column */ @@ -730,7 +751,7 @@ void __guac_terminal_display_flush_clear(guac_terminal_display* display) { /* Mark clear operations as NOP */ if (rect_current->type == GUAC_CHAR_SET && !guac_terminal_has_glyph(rect_current->character.value) - && guac_terminal_colorcmp(joining_color, color) == 0) + && guac_terminal_colorcmp(joining_color, &color) == 0) rect_current->type = GUAC_CHAR_NOP; /* Next column */ @@ -750,7 +771,7 @@ void __guac_terminal_display_flush_clear(guac_terminal_display* display) { row * display->char_height, rect_width * display->char_width, rect_height * display->char_height, - color->red, color->green, color->blue, + color.red, color.green, color.blue, 0xFF); } /* end if clear operation */ diff --git a/src/terminal/palette.c b/src/terminal/palette.c index cdbe4156..a6b2d5ae 100644 --- a/src/terminal/palette.c +++ b/src/terminal/palette.c @@ -289,6 +289,10 @@ const guac_terminal_color GUAC_TERMINAL_INITIAL_PALETTE[256] = { int guac_terminal_colorcmp(const guac_terminal_color* a, const guac_terminal_color* b) { + /* Compare palette index alone if not unknown */ + if (a->palette_index != -1 && b->palette_index != -1) + return a->palette_index - b->palette_index; + /* Consider red component highest order ... */ if (a->red != b->red) return a->red - b->red; diff --git a/src/terminal/terminal/palette.h b/src/terminal/terminal/palette.h index 7f467258..b1524b5a 100644 --- a/src/terminal/terminal/palette.h +++ b/src/terminal/terminal/palette.h @@ -24,6 +24,20 @@ #include +/** + * The pseudo-index of the color set as the the default foreground color for + * the terminal. Regardless of what changes are made to the palette, this index + * will always return the current default foreground color. + */ +#define GUAC_TERMINAL_COLOR_FOREGROUND -2 + +/** + * The pseudo-index of the color set as the the default background color for + * the terminal. Regardless of what changes are made to the palette, this index + * will always return the current default background color. + */ +#define GUAC_TERMINAL_COLOR_BACKGROUND -3 + /** * The index of black within the terminal color palette. */ From f293c5e9c076d583f2f513a7673928da4395814c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 24 Sep 2018 01:28:30 -0700 Subject: [PATCH 045/175] GUACAMOLE-630: Do not entirely reset terminal state when color scheme is changed. --- src/terminal/terminal.c | 9 +++++---- src/terminal/terminal/terminal.h | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index c12df4b9..1984185d 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -1954,15 +1954,16 @@ void guac_terminal_apply_color_scheme(guac_terminal* terminal, display->default_palette); /* Reinitialize default attributes of buffer and display */ - terminal->buffer->default_character = *default_char; + guac_terminal_display_reset_palette(display); display->default_foreground = default_char->attributes.foreground; display->default_background = default_char->attributes.background; - /* Redraw background with new color */ + /* Redraw terminal text and background */ guac_terminal_repaint_default_layer(terminal, client->socket); + __guac_terminal_redraw_rect(terminal, 0, 0, + terminal->term_height - 1, + terminal->term_width - 1); - /* Force reset of terminal state */ - guac_terminal_reset(terminal); guac_terminal_notify(terminal); } diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 6de984bf..28085f67 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -1074,9 +1074,8 @@ int guac_terminal_available_scroll(guac_terminal* term); /** * Immediately applies the given color scheme to the given terminal, overriding - * the color scheme provided when the terminal was created. Applying the color - * scheme implicitly clears the display and resets the terminal state. Valid - * color schemes are those accepted by guac_terminal_parse_color_scheme(). + * the color scheme provided when the terminal was created. Valid color schemes + * are those accepted by guac_terminal_parse_color_scheme(). * * @param terminal * The terminal to apply the color scheme to. From 454682979e057b88a1f75fc60c47af4ba59b73f0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 21 Oct 2018 23:30:53 -0700 Subject: [PATCH 046/175] GUACAMOLE-649: Add support for setting LANG environment variable via SSH. --- src/protocols/ssh/settings.c | 17 +++++++++++++++++ src/protocols/ssh/settings.h | 6 ++++++ src/protocols/ssh/ssh.c | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index e0be6cf3..82f7eb20 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -60,6 +60,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "backspace", "terminal-type", "scrollback", + "locale", NULL }; @@ -238,6 +239,14 @@ enum SSH_ARGS_IDX { */ IDX_SCROLLBACK, + /** + * The locale that should be forwarded to the remote system via the LANG + * environment variable. By default, no locale is forwarded. This setting + * will only have an effect if the SSH server allows the LANG environment + * variable to be set. + */ + IDX_LOCALE, + SSH_ARGS_COUNT }; @@ -396,6 +405,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_TERMINAL_TYPE, "linux"); + /* Read locale */ + settings->locale = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_LOCALE, NULL); + /* Parsing was successful */ return settings; @@ -435,6 +449,9 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) { /* Free terminal emulator type. */ free(settings->terminal_type); + /* Free locale */ + free(settings->locale); + /* Free overall structure */ free(settings); diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 81dcd699..03abd91d 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -248,6 +248,12 @@ typedef struct guac_ssh_settings { */ char* terminal_type; + /** + * The locale that should be forwarded to the remote system via the LANG + * environment variable. + */ + char* locale; + } guac_ssh_settings; /** diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index cddb2fed..fa2aaf67 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -320,6 +320,17 @@ void* ssh_client_thread(void* data) { return NULL; } + /* Forward specified locale */ + if (settings->locale != NULL) { + if (libssh2_channel_setenv(ssh_client->term_channel, "LANG", + settings->locale)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + "Unable to forward locale: SSH server refused to set " + "\"LANG\" environment variable."); + return NULL; + } + } + /* If a command is specified, run that instead of a shell */ if (settings->command != NULL) { if (libssh2_channel_exec(ssh_client->term_channel, settings->command)) { From edbdd08476df063297af0f8977e2df59c7c4fd02 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 8 Nov 2018 19:29:10 -0800 Subject: [PATCH 047/175] GUACAMOLE-649: Allow SSH connection to continue despite failure to set LANG environment variable. --- src/protocols/ssh/ssh.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index fa2aaf67..80b84de1 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -324,10 +324,9 @@ void* ssh_client_thread(void* data) { if (settings->locale != NULL) { if (libssh2_channel_setenv(ssh_client->term_channel, "LANG", settings->locale)) { - guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, + guac_client_log(client, GUAC_LOG_WARNING, "Unable to forward locale: SSH server refused to set " "\"LANG\" environment variable."); - return NULL; } } From 0cf24219d83dd9b9b68e7456400f58087f3815d8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 9 Nov 2018 20:27:28 -0800 Subject: [PATCH 048/175] GUACAMOLE-630: Define maximum "argv" stream length for SSH and telnet with symbolic constants. --- src/protocols/ssh/argv.c | 2 +- src/protocols/ssh/argv.h | 6 ++++++ src/protocols/telnet/argv.c | 2 +- src/protocols/telnet/argv.h | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index 85dca943..00a2cbba 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -38,7 +38,7 @@ typedef struct guac_ssh_argv { /** * Buffer space for containing the received argument value. */ - char buffer[16384]; + char buffer[GUAC_SSH_ARGV_MAX_LENGTH]; /** * The number of bytes received so far. diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h index 27b08cbf..88cf8e97 100644 --- a/src/protocols/ssh/argv.h +++ b/src/protocols/ssh/argv.h @@ -25,6 +25,12 @@ #include +/** + * The maximum number of bytes to allow for any argument value received via an + * argv stream, including null terminator. + */ +#define GUAC_SSH_ARGV_MAX_LENGTH 16384 + /** * Handles an incoming stream from a Guacamole "argv" instruction, updating the * given connection parameter if that parameter is allowed to be updated. diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index da405aa9..07359286 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -38,7 +38,7 @@ typedef struct guac_telnet_argv { /** * Buffer space for containing the received argument value. */ - char buffer[16384]; + char buffer[GUAC_TELNET_ARGV_MAX_LENGTH]; /** * The number of bytes received so far. diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h index b1567ea8..aa13dda9 100644 --- a/src/protocols/telnet/argv.h +++ b/src/protocols/telnet/argv.h @@ -25,6 +25,12 @@ #include +/** + * The maximum number of bytes to allow for any argument value received via an + * argv stream, including null terminator. + */ +#define GUAC_TELNET_ARGV_MAX_LENGTH 16384 + /** * Handles an incoming stream from a Guacamole "argv" instruction, updating the * given connection parameter if that parameter is allowed to be updated. From 9e28de70ec43e6a260177242fa5de9347cfbd66b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Oct 2018 23:38:03 -0700 Subject: [PATCH 049/175] GUACAMOLE-630: Separate setting of font family/size from terminal display initialization. --- src/terminal/display.c | 127 ++++++++++++++++++++++--------- src/terminal/terminal.c | 32 ++++++++ src/terminal/terminal/display.h | 30 ++++++++ src/terminal/terminal/terminal.h | 38 +++++++++ 4 files changed, 190 insertions(+), 37 deletions(-) diff --git a/src/terminal/display.c b/src/terminal/display.c index 1c803b41..d148b411 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -206,15 +206,15 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, guac_terminal_color* foreground, guac_terminal_color* background, guac_terminal_color (*palette)[256]) { - PangoFontMap* font_map; - PangoFont* font; - PangoFontMetrics* metrics; - PangoContext* context; - /* Allocate display */ guac_terminal_display* display = malloc(sizeof(guac_terminal_display)); display->client = client; + /* Initially no font loaded */ + display->font_desc = NULL; + display->char_width = 0; + display->char_height = 0; + /* Create default surface */ display->display_layer = guac_client_alloc_layer(client); display->select_layer = guac_client_alloc_layer(client); @@ -225,42 +225,10 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, guac_protocol_send_move(client->socket, display->select_layer, display->display_layer, 0, 0, 0); - /* Get font */ - display->font_desc = pango_font_description_new(); - pango_font_description_set_family(display->font_desc, font_name); - pango_font_description_set_weight(display->font_desc, PANGO_WEIGHT_NORMAL); - pango_font_description_set_size(display->font_desc, - font_size * PANGO_SCALE * dpi / 96); - - font_map = pango_cairo_font_map_get_default(); - context = pango_font_map_create_context(font_map); - - font = pango_font_map_load_font(font_map, context, display->font_desc); - if (font == NULL) { - guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to get font \"%s\"", font_name); - free(display); - return NULL; - } - - metrics = pango_font_get_metrics(font, NULL); - if (metrics == NULL) { - guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Unable to get font metrics for font \"%s\"", font_name); - free(display); - return NULL; - } - display->default_foreground = display->glyph_foreground = *foreground; display->default_background = display->glyph_background = *background; display->default_palette = palette; - /* Calculate character dimensions */ - display->char_width = - pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; - display->char_height = - (pango_font_metrics_get_descent(metrics) - + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; - /* Initially empty */ display->width = 0; display->height = 0; @@ -269,12 +237,23 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, /* Initially nothing selected */ display->text_selected = false; + /* Attempt to load font */ + if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) { + guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to set initial font \"%s\"", font_name); + free(display); + return NULL; + } + return display; } void guac_terminal_display_free(guac_terminal_display* display) { + /* Free font description */ + pango_font_description_free(display->font_desc); + /* Free default palette. */ free(display->default_palette); @@ -966,4 +945,78 @@ void guac_terminal_display_clear_select(guac_terminal_display* display) { } +int guac_terminal_display_set_font(guac_terminal_display* display, + const char* font_name, int font_size, int dpi) { + + PangoFontDescription* font_desc; + + /* Build off existing font description if possible */ + if (display->font_desc != NULL) + font_desc = pango_font_description_copy(display->font_desc); + + /* Create new font description if there is nothing to copy */ + else { + font_desc = pango_font_description_new(); + pango_font_description_set_weight(font_desc, PANGO_WEIGHT_NORMAL); + } + + /* Optionally update font name */ + if (font_name != NULL) + pango_font_description_set_family(font_desc, font_name); + + /* Optionally update size */ + if (font_size != -1) { + pango_font_description_set_size(font_desc, + font_size * PANGO_SCALE * dpi / 96); + } + + PangoFontMap* font_map = pango_cairo_font_map_get_default(); + PangoContext* context = pango_font_map_create_context(font_map); + + /* Load font from font map */ + PangoFont* font = pango_font_map_load_font(font_map, context, font_desc); + if (font == NULL) { + guac_client_log(display->client, GUAC_LOG_INFO, "Unable to load " + "font \"%s\"", pango_font_description_get_family(font_desc)); + pango_font_description_free(font_desc); + return 1; + } + + /* Get metrics from loaded font */ + PangoFontMetrics* metrics = pango_font_get_metrics(font, NULL); + if (metrics == NULL) { + guac_client_log(display->client, GUAC_LOG_INFO, "Unable to get font " + "metrics for font \"%s\"", + pango_font_description_get_family(font_desc)); + pango_font_description_free(font_desc); + return 1; + } + + /* Save effective size of current display */ + int pixel_width = display->width * display->char_width; + int pixel_height = display->height * display->char_height; + + /* Calculate character dimensions using metrics */ + display->char_width = + pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; + display->char_height = + (pango_font_metrics_get_descent(metrics) + + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; + + /* Atomically replace old font description */ + PangoFontDescription* old_font_desc = display->font_desc; + display->font_desc = font_desc; + pango_font_description_free(old_font_desc); + + /* Recalculate dimensions which will fit within current surface */ + int new_width = pixel_width / display->char_width; + int new_height = pixel_height / display->char_height; + + /* Resize display if dimensions have changed */ + if (new_width != display->width || new_height != display->height) + guac_terminal_display_resize(display, new_width, new_height); + + return 0; + +} diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 1984185d..cf70f92d 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -360,6 +360,10 @@ guac_terminal* guac_terminal_create(guac_client* client, term->upload_path_handler = NULL; term->file_download_handler = NULL; + /* Set size of available screen area */ + term->outer_width = width; + term->outer_height = height; + /* Init modified flag and conditional */ term->modified = 0; pthread_cond_init(&(term->modified_cond), NULL); @@ -1338,6 +1342,10 @@ int guac_terminal_resize(guac_terminal* terminal, int width, int height) { /* Acquire exclusive access to terminal */ guac_terminal_lock(terminal); + /* Set size of available screen area */ + terminal->outer_width = width; + terminal->outer_height = height; + /* Calculate available display area */ int available_width = width - GUAC_TERMINAL_SCROLLBAR_WIDTH; if (available_width < 0) @@ -1968,3 +1976,27 @@ void guac_terminal_apply_color_scheme(guac_terminal* terminal, } +void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, + int font_size, int dpi) { + + guac_client* client = terminal->client; + guac_terminal_display* display = terminal->display; + + if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) + return; + + /* Resize terminal to fit available region, now that font metrics may be + * different */ + guac_terminal_resize(terminal, terminal->outer_width, + terminal->outer_height); + + /* Redraw terminal text and background */ + guac_terminal_repaint_default_layer(terminal, client->socket); + __guac_terminal_redraw_rect(terminal, 0, 0, + terminal->term_height - 1, + terminal->term_width - 1); + + guac_terminal_notify(terminal); + +} + diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index b1274235..5377cb14 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -334,5 +334,35 @@ void guac_terminal_display_select(guac_terminal_display* display, */ void guac_terminal_display_clear_select(guac_terminal_display* display); +/** + * Alters the font of the terminal display. The available display area and the + * regular grid of character cells will be resized as necessary to compensate + * for any changes in font metrics. + * + * If successful, the terminal itself MUST be manually resized to take into + * account the new character dimensions, and MUST be manually redrawn. Failing + * to do so will result in graphical artifacts. + * + * @param display + * The display whose font family and/or size are being changed. + * + * @param font_name + * The name of the new font family, or NULL if the font family should + * remain unchanged. + * + * @param font_size + * The new font size, in points, or -1 if the font size should remain + * unchanged. + * + * @param dpi + * The resolution of the display in DPI. If the font size will not be + * changed (the font size given is -1), this value is ignored. + * + * @return + * Zero if the font was successfully changed, non-zero otherwise. + */ +int guac_terminal_display_set_font(guac_terminal_display* display, + const char* font_name, int font_size, int dpi); + #endif diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 28085f67..ac1217eb 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -277,6 +277,20 @@ struct guac_terminal { */ int requested_scrollback; + /** + * The width of the space available to all components of the terminal, in + * pixels. This may include space which will not actually be used for + * character rendering. + */ + int outer_width; + + /** + * The height of the space available to all components of the terminal, in + * pixels. This may include space which will not actually be used for + * character rendering. + */ + int outer_height; + /** * The width of the terminal, in pixels. */ @@ -1086,5 +1100,29 @@ int guac_terminal_available_scroll(guac_terminal* term); void guac_terminal_apply_color_scheme(guac_terminal* terminal, const char* color_scheme); +/** + * Alters the font of the terminal. The terminal will automatically be redrawn + * and resized as necessary. If the terminal size changes, the remote side of + * the terminal session must be manually informed of that change or graphical + * artifacts may result. + * + * @param terminal + * The terminal whose font family and/or size are being changed. + * + * @param font_name + * The name of the new font family, or NULL if the font family should + * remain unchanged. + * + * @param font_size + * The new font size, in points, or -1 if the font size should remain + * unchanged. + * + * @param dpi + * The resolution of the display in DPI. If the font size will not be + * changed (the font size given is -1), this value is ignored. + */ +void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, + int font_size, int dpi); + #endif From 5683be0ea3eb241c882e605a1990c898acf3af4c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Oct 2018 23:49:12 -0700 Subject: [PATCH 050/175] GUACAMOLE-630: Allow SSH/telnet font family and size to be updated. --- src/protocols/ssh/argv.c | 118 +++++++++++++++++++++++++++++------- src/protocols/telnet/argv.c | 110 ++++++++++++++++++++++++++------- 2 files changed, 184 insertions(+), 44 deletions(-) diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index 00a2cbba..8db2b242 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -26,15 +26,44 @@ #include #include +#include #include #include +/** + * All SSH connection settings which may be updated by unprivileged users + * through "argv" streams. + */ +typedef enum guac_ssh_argv_setting { + + /** + * The color scheme of the terminal. + */ + GUAC_SSH_ARGV_SETTING_COLOR_SCHEME, + + /** + * The name of the font family used by the terminal. + */ + GUAC_SSH_ARGV_SETTING_FONT_NAME, + + /** + * The size of the font used by the terminal, in points. + */ + GUAC_SSH_ARGV_SETTING_FONT_SIZE + +} guac_ssh_argv_setting; + /** * The value or current status of a connection parameter received over an * "argv" stream. */ typedef struct guac_ssh_argv { + /** + * The specific setting being updated. + */ + guac_ssh_argv_setting setting; + /** * Buffer space for containing the received argument value. */ @@ -81,16 +110,51 @@ static int guac_ssh_argv_blob_handler(guac_user* user, static int guac_ssh_argv_end_handler(guac_user* user, guac_stream* stream) { + int size; + guac_client* client = user->client; - guac_ssh_client* telnet_client = (guac_ssh_client*) client->data; - guac_terminal* terminal = telnet_client->term; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal* terminal = ssh_client->term; /* Append null terminator to value */ guac_ssh_argv* argv = (guac_ssh_argv*) stream->data; argv->buffer[argv->length] = '\0'; - /* Update color scheme */ - guac_terminal_apply_color_scheme(terminal, argv->buffer); + /* Apply changes to chosen setting */ + switch (argv->setting) { + + /* Update color scheme */ + case GUAC_SSH_ARGV_SETTING_COLOR_SCHEME: + guac_terminal_apply_color_scheme(terminal, argv->buffer); + break; + + /* Update font name */ + case GUAC_SSH_ARGV_SETTING_FONT_NAME: + guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + break; + + /* Update font size */ + case GUAC_SSH_ARGV_SETTING_FONT_SIZE: + + /* Update only if font size is sane */ + size = atoi(argv->buffer); + if (size > 0) { + guac_terminal_apply_font(terminal, NULL, size, + ssh_client->settings->resolution); + } + + break; + + } + + /* 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)); + } + free(argv); return 0; @@ -99,28 +163,36 @@ static int guac_ssh_argv_end_handler(guac_user* user, int guac_ssh_argv_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name) { - /* Allow users to update the color scheme */ - if (strcmp(name, "color-scheme") == 0) { + guac_ssh_argv_setting setting; - guac_ssh_argv* argv = malloc(sizeof(guac_ssh_argv)); - argv->length = 0; - - /* Prepare stream to receive argument value */ - stream->blob_handler = guac_ssh_argv_blob_handler; - stream->end_handler = guac_ssh_argv_end_handler; - stream->data = argv; - - /* Signal stream is ready */ - guac_protocol_send_ack(user->socket, stream, "Ready for color " - "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - - } + /* Allow users to update the color scheme and font details */ + if (strcmp(name, "color-scheme") == 0) + setting = GUAC_SSH_ARGV_SETTING_COLOR_SCHEME; + else if (strcmp(name, "font-name") == 0) + setting = GUAC_SSH_ARGV_SETTING_FONT_NAME; + else if (strcmp(name, "font-size") == 0) + setting = GUAC_SSH_ARGV_SETTING_FONT_SIZE; /* No other connection parameters may be updated */ - guac_protocol_send_ack(user->socket, stream, "Not allowed.", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + else { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + guac_ssh_argv* argv = malloc(sizeof(guac_ssh_argv)); + argv->setting = setting; + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_ssh_argv_blob_handler; + stream->end_handler = guac_ssh_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); return 0; diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 07359286..450c7f35 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -29,12 +29,40 @@ #include #include +/** + * All telnet connection settings which may be updated by unprivileged users + * through "argv" streams. + */ +typedef enum guac_telnet_argv_setting { + + /** + * The color scheme of the terminal. + */ + GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME, + + /** + * The name of the font family used by the terminal. + */ + GUAC_TELNET_ARGV_SETTING_FONT_NAME, + + /** + * The size of the font used by the terminal, in points. + */ + GUAC_TELNET_ARGV_SETTING_FONT_SIZE + +} guac_telnet_argv_setting; + /** * The value or current status of a connection parameter received over an * "argv" stream. */ typedef struct guac_telnet_argv { + /** + * The specific setting being updated. + */ + guac_telnet_argv_setting setting; + /** * Buffer space for containing the received argument value. */ @@ -81,6 +109,8 @@ static int guac_telnet_argv_blob_handler(guac_user* user, static int guac_telnet_argv_end_handler(guac_user* user, guac_stream* stream) { + int size; + guac_client* client = user->client; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; guac_terminal* terminal = telnet_client->term; @@ -89,8 +119,38 @@ static int guac_telnet_argv_end_handler(guac_user* user, guac_telnet_argv* argv = (guac_telnet_argv*) stream->data; argv->buffer[argv->length] = '\0'; - /* Update color scheme */ - guac_terminal_apply_color_scheme(terminal, argv->buffer); + /* Apply changes to chosen setting */ + switch (argv->setting) { + + /* Update color scheme */ + case GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME: + guac_terminal_apply_color_scheme(terminal, argv->buffer); + break; + + /* Update font name */ + case GUAC_TELNET_ARGV_SETTING_FONT_NAME: + guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + break; + + /* Update font size */ + case GUAC_TELNET_ARGV_SETTING_FONT_SIZE: + + /* Update only if font size is sane */ + size = atoi(argv->buffer); + if (size > 0) { + guac_terminal_apply_font(terminal, NULL, size, + telnet_client->settings->resolution); + } + + break; + + } + + /* 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); + free(argv); return 0; @@ -99,28 +159,36 @@ static int guac_telnet_argv_end_handler(guac_user* user, int guac_telnet_argv_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name) { - /* Allow users to update the color scheme */ - if (strcmp(name, "color-scheme") == 0) { + guac_telnet_argv_setting setting; - guac_telnet_argv* argv = malloc(sizeof(guac_telnet_argv)); - argv->length = 0; - - /* Prepare stream to receive argument value */ - stream->blob_handler = guac_telnet_argv_blob_handler; - stream->end_handler = guac_telnet_argv_end_handler; - stream->data = argv; - - /* Signal stream is ready */ - guac_protocol_send_ack(user->socket, stream, "Ready for color " - "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - - } + /* Allow users to update the color scheme and font details */ + if (strcmp(name, "color-scheme") == 0) + setting = GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME; + else if (strcmp(name, "font-name") == 0) + setting = GUAC_TELNET_ARGV_SETTING_FONT_NAME; + else if (strcmp(name, "font-size") == 0) + setting = GUAC_TELNET_ARGV_SETTING_FONT_SIZE; /* No other connection parameters may be updated */ - guac_protocol_send_ack(user->socket, stream, "Not allowed.", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + else { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + guac_telnet_argv* argv = malloc(sizeof(guac_telnet_argv)); + argv->setting = setting; + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_telnet_argv_blob_handler; + stream->end_handler = guac_telnet_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); return 0; From ffdc98d024e658dc868c6ba52e250e64dc25c6f1 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 10 Apr 2018 21:31:50 -0400 Subject: [PATCH 051/175] GUACAMOLE-422: Support timezone redirection in RDP via TZ variable. --- src/protocols/rdp/rdp_settings.c | 15 +++++++++++++++ src/protocols/rdp/rdp_settings.h | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index a3bd3482..21114d44 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -79,6 +79,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "disable-glyph-caching", "preconnection-id", "preconnection-blob", + "timezone", #ifdef ENABLE_COMMON_SSH "enable-sftp", @@ -356,6 +357,11 @@ enum RDP_ARGS_IDX { */ IDX_PRECONNECTION_BLOB, + /** + * The timezone to pass through to the RDP connection. + */ + IDX_TIMEZONE, + #ifdef ENABLE_COMMON_SSH /** * "true" if SFTP should be enabled for the RDP connection, "false" or @@ -840,6 +846,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, if (settings->server_layout == NULL) settings->server_layout = guac_rdp_keymap_find(GUAC_DEFAULT_KEYMAP); + /* Timezone if provied by client */ + settings->timezone = + guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_TIMEZONE, NULL); + #ifdef ENABLE_COMMON_SSH /* SFTP enable/disable */ settings->enable_sftp = @@ -1013,6 +1024,7 @@ void guac_rdp_settings_free(guac_rdp_settings* settings) { free(settings->remote_app); free(settings->remote_app_args); free(settings->remote_app_dir); + free(settings->timezone); free(settings->username); free(settings->printer_name); @@ -1265,6 +1277,9 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { #endif /* Device redirection */ + if (guac_settings->timezone) + setenv("TZ", guac_settings->timezone, 1); + #ifdef LEGACY_RDPSETTINGS #ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION rdp_settings->device_redirection = guac_settings->audio_enabled diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index d521a87c..ece399df 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -341,6 +341,11 @@ typedef struct guac_rdp_settings { */ char* preconnection_blob; + /** + * The timezone to pass through to the RDP connection. + */ + char* timezone; + #ifdef ENABLE_COMMON_SSH /** * Whether SFTP should be enabled for the VNC connection. From 5536b836addb27dc6c8433b6afef2f9ad24d399d Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 2 Jun 2018 12:00:22 -0400 Subject: [PATCH 052/175] GUACAMOLE-422: Add support for passing through TZ in SSH. --- src/protocols/ssh/settings.c | 18 ++++++++++++++++++ src/protocols/ssh/settings.h | 4 ++++ src/protocols/ssh/ssh.c | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 82f7eb20..820da51e 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -61,6 +61,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "terminal-type", "scrollback", "locale", + "timezone", NULL }; @@ -246,6 +247,15 @@ enum SSH_ARGS_IDX { * variable to be set. */ IDX_LOCALE, + + /** + * The timezone that is passed from the client system to the + * remote server, or null if not specified. If set, and allowed + * by the remote SSH server, the TZ environment variable will be + * set on the remote session, causing the session to be localized + * to the specified timezone. + */ + IDX_TIMEZONE, SSH_ARGS_COUNT }; @@ -410,6 +420,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_LOCALE, NULL); + /* Read the client timezone. */ + settings->timezone = + guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_TIMEZONE, NULL); + /* Parsing was successful */ return settings; @@ -452,6 +467,9 @@ void guac_ssh_settings_free(guac_ssh_settings* settings) { /* Free locale */ free(settings->locale); + /* Free the client timezone. */ + free(settings->timezone); + /* Free overall structure */ free(settings); diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index 03abd91d..e4d99e14 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -253,6 +253,10 @@ typedef struct guac_ssh_settings { * environment variable. */ char* locale; + /** + * The client timezone to pass to the remote system. + */ + char* timezone; } guac_ssh_settings; diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 80b84de1..9e79454b 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -256,6 +256,10 @@ void* ssh_client_thread(void* data) { return NULL; } + /* Set the client timezone */ + if (settings->timezone != NULL) + libssh2_channel_setenv(ssh_client->term_channel, "TZ", settings->timezone); + #ifdef ENABLE_SSH_AGENT /* Start SSH agent forwarding, if enabled */ if (ssh_client->enable_agent) { From d7ed452d699f19504a861caff187b72709afcd78 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 15:30:17 -0500 Subject: [PATCH 053/175] GUACAMOLE-422: Update comments on timezone settings. --- src/protocols/rdp/rdp_settings.c | 5 ++++- src/protocols/ssh/settings.c | 11 ++++++----- src/protocols/ssh/settings.h | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 21114d44..f37c156e 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -358,7 +358,10 @@ enum RDP_ARGS_IDX { IDX_PRECONNECTION_BLOB, /** - * The timezone to pass through to the RDP connection. + * The timezone to pass through to the RDP connection, in IANA format, which + * will be translated into Windows formats. See the following page for + * information and list of valid values: + * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ IDX_TIMEZONE, diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 820da51e..962524ce 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -249,11 +249,12 @@ enum SSH_ARGS_IDX { IDX_LOCALE, /** - * The timezone that is passed from the client system to the - * remote server, or null if not specified. If set, and allowed - * by the remote SSH server, the TZ environment variable will be - * set on the remote session, causing the session to be localized - * to the specified timezone. + * The timezone that is to be passed to the remote system, via the + * TZ environment variable. By default, no timezone is forwarded + * and the timezone of the remote system will be used. This + * setting will only work if the SSH server allows the TZ variable + * to be set. Timezones should be in standard IANA format, see: + * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ IDX_TIMEZONE, diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index e4d99e14..baa634ad 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -253,6 +253,7 @@ typedef struct guac_ssh_settings { * environment variable. */ char* locale; + /** * The client timezone to pass to the remote system. */ From e2b4de9d95571609e28c63d4b02583df6e4574a7 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 15:45:24 -0500 Subject: [PATCH 054/175] GAUCAMOLE-422: Add warning messages when TZ cannot be set. --- src/protocols/rdp/rdp_settings.c | 11 +++++++++-- src/protocols/ssh/ssh.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index f37c156e..78e97078 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -1280,8 +1280,15 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { #endif /* Device redirection */ - if (guac_settings->timezone) - setenv("TZ", guac_settings->timezone, 1); + if (guac_settings->timezone) { + + /* Set the TZ env variable */ + if (setenv("TZ", guac_settings->timezone, 1)) { + guac_user_log(user, GUAC_LOG_WARNING, "Could not set TZ " + "variable. Received error %i", errno); + } + + } #ifdef LEGACY_RDPSETTINGS #ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 9e79454b..22ba960f 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -257,8 +257,15 @@ void* ssh_client_thread(void* data) { } /* Set the client timezone */ - if (settings->timezone != NULL) - libssh2_channel_setenv(ssh_client->term_channel, "TZ", settings->timezone); + if (settings->timezone != NULL) { + if (libssh2_channel_setenv(ssh_client->term_channel, "TZ", + settings->timezone)) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unable to set the timzeone: SSH server " + "refused to set \"TZ\" variable."); + } + } + #ifdef ENABLE_SSH_AGENT /* Start SSH agent forwarding, if enabled */ From 0b715590170139a17c33b931d4d10ee2ce9fb163 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 15:48:02 -0500 Subject: [PATCH 055/175] GUACAMOLE-422: Add errno header. --- src/protocols/rdp/rdp_settings.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 78e97078..27553e3b 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -35,6 +35,7 @@ #include "compat/winpr-wtypes.h" #endif +#include #include #include From b3be9eb8692e6c1c4516f0905cb7e113d18dd190 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 16:07:20 -0500 Subject: [PATCH 056/175] GUACAMOLE-422: Revert addition of logging for setting TZ variable. --- src/protocols/rdp/rdp_settings.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 27553e3b..e9b81c21 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -1281,15 +1281,8 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { #endif /* Device redirection */ - if (guac_settings->timezone) { - - /* Set the TZ env variable */ - if (setenv("TZ", guac_settings->timezone, 1)) { - guac_user_log(user, GUAC_LOG_WARNING, "Could not set TZ " - "variable. Received error %i", errno); - } - - } + if (guac_settings->timezone) + setenv("TZ", guac_settings->timezone, 1) #ifdef LEGACY_RDPSETTINGS #ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION From 4bd19160ded3e824fb46ea066b08182eafffdbeb Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 17:22:03 -0500 Subject: [PATCH 057/175] GUACAMOLE-422: Add logging for RDP timzeone. --- src/protocols/rdp/rdp.c | 2 +- src/protocols/rdp/rdp_settings.c | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 4d484320..042d3893 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -719,7 +719,7 @@ static int guac_rdp_handle_connection(guac_client* client) { guac_common_cursor_set_pointer(rdp_client->display->cursor); /* Push desired settings to FreeRDP */ - guac_rdp_push_settings(settings, rdp_inst); + guac_rdp_push_settings(client, settings, rdp_inst); /* Connect to RDP server */ if (!freerdp_connect(rdp_inst)) { diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index e9b81c21..ed77bf62 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -1169,7 +1169,8 @@ static char* guac_rdp_strdup(const char* str) { } -void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { +void guac_rdp_push_settings(guac_client* client, + guac_rdp_settings* guac_settings, freerdp* rdp) { BOOL bitmap_cache = !guac_settings->disable_bitmap_caching; rdpSettings* rdp_settings = rdp->settings; @@ -1280,10 +1281,15 @@ void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp) { #endif #endif - /* Device redirection */ - if (guac_settings->timezone) - setenv("TZ", guac_settings->timezone, 1) + /* Timezone redirection */ + if (guac_settings->timezone) { + if(setenv("TZ", guac_settings->timezone, 1)) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unable to set TZ variable, error %i", errno); + } + } + /* Device redirection */ #ifdef LEGACY_RDPSETTINGS #ifdef HAVE_RDPSETTINGS_DEVICEREDIRECTION rdp_settings->device_redirection = guac_settings->audio_enabled From 9a944637be73570857034ab492342c54dd742cfd Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 17:25:12 -0500 Subject: [PATCH 058/175] GUACAMOLE-422: Fix function declaration for pushing settings. --- src/protocols/rdp/rdp_settings.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index ece399df..6955ed58 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -552,13 +552,17 @@ extern const char* GUAC_RDP_CLIENT_ARGS[]; /** * Save all given settings to the given freerdp instance. * + * @param client + * The guac_client object providing the settings. + * * @param guac_settings * The guac_rdp_settings object to save. * * @param rdp * The RDP instance to save settings to. */ -void guac_rdp_push_settings(guac_rdp_settings* guac_settings, freerdp* rdp); +void guac_rdp_push_settings(guac_client* client, + guac_rdp_settings* guac_settings, freerdp* rdp); /** * Returns the width of the RDP session display. From f61539c4e75c9e55fbfcf3a71a0ed0c3548bbb4e Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 12 Nov 2018 11:55:07 -0500 Subject: [PATCH 059/175] GUACAMOLE-422: Quick fixes for style and logging. --- src/protocols/rdp/rdp_settings.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index ed77bf62..810253dd 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -1283,9 +1283,10 @@ void guac_rdp_push_settings(guac_client* client, /* Timezone redirection */ if (guac_settings->timezone) { - if(setenv("TZ", guac_settings->timezone, 1)) { + if (setenv("TZ", guac_settings->timezone, 1)) { guac_client_log(client, GUAC_LOG_WARNING, - "Unable to set TZ variable, error %i", errno); + "Unable to forward timezone: TZ environment variable " + "could not be set: %s", sterror(errno)); } } From d1b369528288be97f79786bbf517fef185f6d4e0 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 12 Nov 2018 11:56:57 -0500 Subject: [PATCH 060/175] GUACAMOLE-422: Fix type in strerror() --- src/protocols/rdp/rdp_settings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 810253dd..88d9bc7f 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -1286,7 +1286,7 @@ void guac_rdp_push_settings(guac_client* client, if (setenv("TZ", guac_settings->timezone, 1)) { guac_client_log(client, GUAC_LOG_WARNING, "Unable to forward timezone: TZ environment variable " - "could not be set: %s", sterror(errno)); + "could not be set: %s", strerror(errno)); } } From 7b1ba3f2699de9a59d7819b6edc6992e02117e27 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 12 Nov 2018 12:09:51 -0500 Subject: [PATCH 061/175] GUACAMOLE-422: Fix spelling mistake. --- src/protocols/ssh/ssh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 22ba960f..9db545bf 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -261,7 +261,7 @@ void* ssh_client_thread(void* data) { if (libssh2_channel_setenv(ssh_client->term_channel, "TZ", settings->timezone)) { guac_client_log(client, GUAC_LOG_WARNING, - "Unable to set the timzeone: SSH server " + "Unable to set the timezone: SSH server " "refused to set \"TZ\" variable."); } } From 867e63b52473d962183eac98240ea4831286ab90 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Oct 2018 12:43:08 -0700 Subject: [PATCH 062/175] GUACAMOLE-638: avcodec_register_all() should be used only if not deprecated. --- src/guacenc/guacenc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index e8442fd9..093623a2 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -75,8 +75,10 @@ int main(int argc, char* argv[]) { guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " "version " VERSION); +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) /* Prepare libavcodec */ avcodec_register_all(); +#endif /* Track number of overall failures */ int total_files = argc - optind; From 2827af33d672b2b34ba9ede3a81f220c2c4a3954 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Nov 2018 21:35:16 -0800 Subject: [PATCH 063/175] GUACAMOLE-662: Correct fork logic (main test process should be PARENT, not child). --- tests/protocol/instruction_read.c | 2 +- tests/protocol/instruction_write.c | 2 +- tests/protocol/nest_write.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/protocol/instruction_read.c b/tests/protocol/instruction_read.c index f10e0017..930b5b5e 100644 --- a/tests/protocol/instruction_read.c +++ b/tests/protocol/instruction_read.c @@ -55,7 +55,7 @@ void test_instruction_read() { } /* Child (pipe writer) */ - if (childpid != 0) { + if (childpid == 0) { close(rfd); CU_ASSERT_EQUAL( write(wfd, test_string, sizeof(test_string)), diff --git a/tests/protocol/instruction_write.c b/tests/protocol/instruction_write.c index 5a21b608..8e12dfc1 100644 --- a/tests/protocol/instruction_write.c +++ b/tests/protocol/instruction_write.c @@ -50,7 +50,7 @@ void test_instruction_write() { } /* Child (pipe writer) */ - if (childpid != 0) { + if (childpid == 0) { guac_socket* socket; diff --git a/tests/protocol/nest_write.c b/tests/protocol/nest_write.c index 5d4c231e..e1b885b2 100644 --- a/tests/protocol/nest_write.c +++ b/tests/protocol/nest_write.c @@ -50,7 +50,7 @@ void test_nest_write() { } /* Child (pipe writer) */ - if (childpid != 0) { + if (childpid == 0) { guac_socket* nested_socket; guac_socket* socket; From d7118fda707cf888eede291d547aee5aa5e293c1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Nov 2018 13:24:23 -0800 Subject: [PATCH 064/175] GUACAMOLE-662: Add utility script for automatically generating CUnit test runners. --- Makefile.am | 19 ++--- util/generate-test-runner.pl | 146 +++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 9 deletions(-) create mode 100755 util/generate-test-runner.pl diff --git a/Makefile.am b/Makefile.am index 6926474b..cbfea857 100644 --- a/Makefile.am +++ b/Makefile.am @@ -91,13 +91,14 @@ if ENABLE_GUACLOG SUBDIRS += src/guaclog endif -EXTRA_DIST = \ - .dockerignore \ - CONTRIBUTING \ - Dockerfile \ - LICENSE \ - NOTICE \ - bin/guacctl \ - doc/Doxyfile.in \ - src/guacd-docker +EXTRA_DIST = \ + .dockerignore \ + CONTRIBUTING \ + Dockerfile \ + LICENSE \ + NOTICE \ + bin/guacctl \ + doc/Doxyfile.in \ + src/guacd-docker \ + util/generate-test-runner.pl diff --git a/util/generate-test-runner.pl b/util/generate-test-runner.pl new file mode 100755 index 00000000..c32ec0c4 --- /dev/null +++ b/util/generate-test-runner.pl @@ -0,0 +1,146 @@ +#!/usr/bin/perl +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# generate-test-runner.pl +# +# Generates a test runner for the .c files given on the command line. Each .c +# file may declare any number of tests so long as each test uses CUnit and is +# declared with the following convention: +# +# void test_SUITENAME__TESTNAME() { +# ... +# } +# +# where TESTNAME is the arbitrary name of the test and SUITENAME is the +# arbitrary name of the test suite that this test belongs to. +# +# Absolutely all tests MUST follow the above convention if they are to be +# picked up by this script. Functions which are not tests MUST NOT follow +# the above convention. +# + +use strict; + +# Parse all test declarations from given file +my %test_suites = (); +while (<>) { + if ((my $suite_name, my $test_name) = m/^void\s+test_(\w+)__(\w+)/) { + $test_suites{$suite_name} //= (); + push @{$test_suites{$suite_name}}, $test_name; + } +} + +# +# Common test runner header +# + +print <<'END'; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +END + +# +# Prototypes for all test functions +# + +while ((my $suite_name, my $test_names) = each (%test_suites)) { + print "\n/* Automatically-generated prototypes for the $suite_name suite */\n"; + foreach my $test_name (@{ $test_names }) { + print "void test_${suite_name}__${test_name}();\n"; + } +} + +# +# Beginning of main() function body for test runner +# + +print <<"END"; + +/* Automatically-generated test runner */ +int main() { + + /* Init CUnit test registry */ + if (CU_initialize_registry() != CUE_SUCCESS) + return CU_get_error(); +END + +# +# Within main(), register each test and its corresponding test suite +# + +while ((my $suite_name, my $test_names) = each (%test_suites)) { + + print <<" END"; + + /* Create and register all tests for the $suite_name suite */ + CU_pSuite $suite_name = CU_add_suite("$suite_name", NULL, NULL); + if ($suite_name == NULL + END + + foreach my $test_name (@{ $test_names }) { + print <<" END"; + || CU_add_test($suite_name, "$test_name", test_${suite_name}__${test_name}) == NULL + END + } + + print <<" END"; + ) goto cleanup; + END + +} + +# +# End of main() function +# + +print <<"END"; + + /* Run all tests in all suites */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + +cleanup: + /* Tests complete */ + CU_cleanup_registry(); + return CU_get_error(); + +} +END + From ca4009c9824b26af9c2785fc847dfe7f9f14bc1d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Nov 2018 19:55:06 -0800 Subject: [PATCH 065/175] GUACAMOLE-662: Log test output in TAP format. --- util/generate-test-runner.pl | 56 +++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/util/generate-test-runner.pl b/util/generate-test-runner.pl index c32ec0c4..a121a071 100755 --- a/util/generate-test-runner.pl +++ b/util/generate-test-runner.pl @@ -39,15 +39,23 @@ use strict; -# Parse all test declarations from given file +my $num_tests = 0; my %test_suites = (); + +# Parse all test declarations from given file while (<>) { if ((my $suite_name, my $test_name) = m/^void\s+test_(\w+)__(\w+)/) { + $num_tests++; $test_suites{$suite_name} //= (); push @{$test_suites{$suite_name}}, $test_name; } } +# Bail out if there's nothing to write +if ($num_tests == 0) { + die "No unit tests... :(\n"; +} + # # Common test runner header # @@ -73,7 +81,44 @@ print <<'END'; */ #include -#include +#include + +/** + * The current test number, as required by the TAP format. This value is + * automatically incremented by tap_log_test_completed() after each test is + * run. + */ +int tap_test_number = 1; + +/** + * Logs the status of a CUnit test which just completed. This implementation + * logs test completion in TAP format. + * + * @param test + * The CUnit test which just completed. + * + * @param suite + * The CUnit test suite associated with the test. + * + * @param failure + * The head element of the test failure list, or NULL if the test passed. + */ +static void tap_log_test_completed(const CU_pTest test, + const CU_pSuite suite, const CU_pFailureRecord failure) { + + /* Log success/failure in TAP format */ + if (failure == NULL) + printf("ok %i - [%s] %s: OK\n", + tap_test_number, suite->pName, test->pName); + else + printf("not ok %i - [%s] %s: Assertion failed on %s:%i: %s\n", + tap_test_number, suite->pName, test->pName, + failure->strFileName, failure->uiLineNumber, + failure->strCondition); + + tap_test_number++; + +} END # @@ -132,9 +177,12 @@ while ((my $suite_name, my $test_names) = each (%test_suites)) { print <<"END"; + /* Write TAP header */ + printf("1..$num_tests\\n"); + /* Run all tests in all suites */ - CU_basic_set_mode(CU_BRM_VERBOSE); - CU_basic_run_tests(); + CU_set_test_complete_handler(tap_log_test_completed); + CU_run_all_tests(); cleanup: /* Tests complete */ From 877bf59cb6f64cf6ca663cfedd65efdc579874e8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Nov 2018 20:34:29 -0800 Subject: [PATCH 066/175] GUACAMOLE-662: Force line-buffered output. --- util/generate-test-runner.pl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/generate-test-runner.pl b/util/generate-test-runner.pl index a121a071..7534f805 100755 --- a/util/generate-test-runner.pl +++ b/util/generate-test-runner.pl @@ -177,6 +177,11 @@ while ((my $suite_name, my $test_names) = each (%test_suites)) { print <<"END"; + /* Force line-buffered output to ensure log messages are visible even if + * a test crashes */ + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + /* Write TAP header */ printf("1..$num_tests\\n"); From 476b43104103fb61363f0e451e6553ebac45d157 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 13 Nov 2018 13:26:43 -0800 Subject: [PATCH 067/175] GUACAMOLE-662: Migrate tests to test runners generated by new convenience script. Remove unnecessary test runners. --- .gitignore | 15 +- Makefile.am | 6 +- README-unit-testing.md | 89 ++++++ configure.ac | 7 +- src/common/.gitignore | 9 + src/common/Makefile.am | 1 + src/common/tests/Makefile.am | 72 +++++ .../common/tests/iconv/convert.c | 175 +++++++---- src/common/tests/rect/clip_and_split.c | 156 ++++++++++ .../common/tests/rect/constrain.c | 36 +-- src/common/tests/rect/expand_to_grid.c | 71 +++++ .../common/tests/rect/extend.c | 43 +-- .../common/tests/rect/init.c | 21 +- src/common/tests/rect/intersects.c | 91 ++++++ .../common/tests/string/count_occurrences.c | 32 +- .../common/tests/string/split.c | 23 +- src/libguac/.gitignore | 44 +-- src/libguac/Makefile.am | 1 + {tests => src/libguac/tests}/Makefile.am | 62 ++-- .../libguac/tests}/client/buffer_pool.c | 19 +- .../libguac/tests}/client/layer_pool.c | 19 +- .../libguac/tests/parser/append.c | 15 +- src/libguac/tests/parser/read.c | 145 +++++++++ .../libguac/tests/pool/next_free.c | 31 +- .../libguac/tests}/protocol/base64_decode.c | 16 +- .../tests/socket/fd_send_instruction.c | 136 +++++++++ .../tests/socket/nested_send_instruction.c | 149 +++++++++ src/libguac/tests/unicode/charsize.c | 33 ++ src/libguac/tests/unicode/read.c | 52 ++++ .../libguac/tests/unicode/strlen.c | 48 +-- .../libguac/tests/unicode/write.c | 48 +-- tests/client/client_suite.c | 56 ---- tests/common/common_suite.c | 57 ---- tests/common/guac_rect.c | 289 ------------------ tests/protocol/instruction_read.c | 108 ------- tests/protocol/instruction_write.c | 101 ------ tests/protocol/nest_write.c | 109 ------- tests/protocol/suite.c | 59 ---- tests/util/util_suite.c | 56 ---- 39 files changed, 1311 insertions(+), 1189 deletions(-) create mode 100644 README-unit-testing.md create mode 100644 src/common/.gitignore create mode 100644 src/common/tests/Makefile.am rename tests/common/guac_iconv.c => src/common/tests/iconv/convert.c (52%) create mode 100644 src/common/tests/rect/clip_and_split.c rename tests/test_libguac.c => src/common/tests/rect/constrain.c (60%) create mode 100644 src/common/tests/rect/expand_to_grid.c rename tests/common/common_suite.h => src/common/tests/rect/extend.c (56%) rename tests/client/client_suite.h => src/common/tests/rect/init.c (68%) create mode 100644 src/common/tests/rect/intersects.c rename tests/protocol/suite.h => src/common/tests/string/count_occurrences.c (56%) rename tests/common/guac_string.c => src/common/tests/string/split.c (79%) rename {tests => src/libguac/tests}/Makefile.am (59%) rename {tests => src/libguac/tests}/client/buffer_pool.c (84%) rename {tests => src/libguac/tests}/client/layer_pool.c (84%) rename tests/protocol/instruction_parse.c => src/libguac/tests/parser/append.c (89%) create mode 100644 src/libguac/tests/parser/read.c rename tests/util/guac_pool.c => src/libguac/tests/pool/next_free.c (78%) rename {tests => src/libguac/tests}/protocol/base64_decode.c (90%) create mode 100644 src/libguac/tests/socket/fd_send_instruction.c create mode 100644 src/libguac/tests/socket/nested_send_instruction.c create mode 100644 src/libguac/tests/unicode/charsize.c create mode 100644 src/libguac/tests/unicode/read.c rename tests/util/util_suite.h => src/libguac/tests/unicode/strlen.c (55%) rename tests/util/guac_unicode.c => src/libguac/tests/unicode/write.c (50%) delete mode 100644 tests/client/client_suite.c delete mode 100644 tests/common/common_suite.c delete mode 100644 tests/common/guac_rect.c delete mode 100644 tests/protocol/instruction_read.c delete mode 100644 tests/protocol/instruction_write.c delete mode 100644 tests/protocol/nest_write.c delete mode 100644 tests/protocol/suite.c delete mode 100644 tests/util/util_suite.c diff --git a/.gitignore b/.gitignore index dec0cd10..e0a6d53e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,27 +28,16 @@ Makefile Makefile.in aclocal.m4 autom4te.cache/ +build-aux/ +libtool m4/* !README -compile -config.guess config.h config.h.in config.log config.status -config.sub configure -depcomp -install-sh -libtool -ltmain.sh -missing stamp-h1 -test-driver - -# Test binaries -tests/test_* -!tests/test_*.[ch] # Generated docs doc/doxygen-output diff --git a/Makefile.am b/Makefile.am index cbfea857..c75735c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,13 +39,11 @@ DIST_SUBDIRS = \ src/protocols/rdp \ src/protocols/ssh \ src/protocols/telnet \ - src/protocols/vnc \ - tests + src/protocols/vnc SUBDIRS = \ src/libguac \ - src/common \ - tests + src/common if ENABLE_COMMON_SSH SUBDIRS += src/common-ssh diff --git a/README-unit-testing.md b/README-unit-testing.md new file mode 100644 index 00000000..5bed8721 --- /dev/null +++ b/README-unit-testing.md @@ -0,0 +1,89 @@ + +Unit testing and guacamole-server +================================= + +Unit tests within guacamole-server are implemented using the following: + +* automake, which allows arbitrary tests to be declared within `Makefile.am` + and uses `make check` to run those tests. +* CUnit (libcunit), a unit testing framework. +* `util/generate-test-runner.pl`, a Perl script which generates a test runner + written in C which leverages CUnit, running the unit tests declared in each + of the given `.c` files. The generated test runner produces output in [TAP + format](https://testanything.org/) which is consumed by the TAP test driver + provided by automake. + +Writing unit tests +------------------ + +All unit tests should be within reasonably-isolated C source files, with each +logical test having its own function of the form: + + void test_SUITENAME__TESTNAME() { + ... + } + +where `TESTNAME` is the arbitrary name of the test and `SUITENAME` is the +arbitrary name of the test suite that this test belongs to. + +**This naming convention is required by `generate-test-runner.pl`.** Absolutely +all tests MUST follow the above convention if they are to be picked up and +organized by the test runner generation script. Functions which are not tests +MUST NOT follow the above convention so that they are _not_ picked up mistakenly +by the test runner generator as if they were tests. + +The `Makefile.am` for a subproject which contains such tests is typically +modified to contain a sections like the following: + + # + # Unit tests for myproj + # + + check_PROGRAMS = test_myproj + TESTS = $(check_PROGRAMS) + + test_myproj_SOURCES = \ + ...all source files... + + test_myproj_CFLAGS = \ + -Werror -Wall -pedantic \ + ...other flags... + + test_myproj_LDADD = \ + ...libraries... + + # + # Autogenerate test runner + # + + GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl + CLEANFILES = _generated_runner.c + + _generated_runner.c: $(test_myproj_SOURCES) + $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + + nodist_test_libguac_SOURCES = \ + _generated_runner.c + + # Use automake's TAP test driver for running any tests + LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh + +The above declares ... + +* ... that a binary, `test_myproj` should be built from the given sources. + Note that `test_myproj_SOURCES` contains only the source which was actually + written by hand while `nodist_test_myproj_SOURCES` contains only the source + which was generated by `generate-test-runner.pl`. +* ... that this `test_myproj` binary should be run to test this project when + `make check` is run, and that automake's TAP driver should be used to + consume its output. +* ... that the `_generated_runner.c` source file is generated dynamically + (through running `generate-test-runner.pl` on all non-generated test source) + and should not be distributed as part of the source archive. + +With tests following the above naming convention in place, and with the +necessary changes made to the applicable `Makefile.am`, all tests will be +run automatically when `make check` is run. + diff --git a/configure.ac b/configure.ac index 672d19eb..b63ed690 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,7 @@ AC_PREREQ([2.61]) AC_INIT([guacamole-server], [1.0.0]) +AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AM_SILENT_RULES([yes]) @@ -28,6 +29,9 @@ LT_INIT([dlopen]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) +# Use TAP test driver for tests (part of automake) +AC_REQUIRE_AUX_FILE([tap-driver.sh]) + # Programs AC_PROG_CC AC_PROG_CC_C99 @@ -1299,11 +1303,12 @@ AM_CONDITIONAL([ENABLE_GUACLOG], [test "x${enable_guaclog}" = "xyes"]) AC_CONFIG_FILES([Makefile doc/Doxyfile - tests/Makefile src/common/Makefile + src/common/tests/Makefile src/common-ssh/Makefile src/terminal/Makefile src/libguac/Makefile + src/libguac/tests/Makefile src/guacd/Makefile src/guacd/man/guacd.8 src/guacd/man/guacd.conf.5 diff --git a/src/common/.gitignore b/src/common/.gitignore new file mode 100644 index 00000000..f7efbda8 --- /dev/null +++ b/src/common/.gitignore @@ -0,0 +1,9 @@ + +# Auto-generated test runner and binary +_generated_runner.c +test_common + +# Test suite output +*.log +*.trs + diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 06190103..f98054be 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -27,6 +27,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 noinst_LTLIBRARIES = libguac_common.la +SUBDIRS = . tests noinst_HEADERS = \ common/io.h \ diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am new file mode 100644 index 00000000..8169d7d7 --- /dev/null +++ b/src/common/tests/Makefile.am @@ -0,0 +1,72 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +# +# Unit tests for libguac_common +# + +check_PROGRAMS = test_common +TESTS = $(check_PROGRAMS) + +test_common_SOURCES = \ + iconv/convert.c \ + rect/clip_and_split.c \ + rect/constrain.c \ + rect/expand_to_grid.c \ + rect/extend.c \ + rect/init.c \ + rect/intersects.c \ + string/count_occurrences.c \ + string/split.c + +test_common_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ + +test_common_LDADD = \ + @COMMON_LTLIB@ \ + @CUNIT_LIBS@ + +# +# Autogenerate test runner +# + +GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl +CLEANFILES = _generated_runner.c + +_generated_runner.c: $(test_common_SOURCES) + $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + +nodist_test_common_SOURCES = \ + _generated_runner.c + +# Use automake's TAP test driver for running any tests +LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh + diff --git a/tests/common/guac_iconv.c b/src/common/tests/iconv/convert.c similarity index 52% rename from tests/common/guac_iconv.c rename to src/common/tests/iconv/convert.c index 6af706f9..1a00994f 100644 --- a/tests/common/guac_iconv.c +++ b/src/common/tests/iconv/convert.c @@ -17,15 +17,75 @@ * under the License. */ -#include "config.h" - -#include "common_suite.h" #include "common/iconv.h" -#include -#include +#include -static void test_conversion( +/** + * UTF8 for "papà è bello". + */ +unsigned char test_string_utf8[] = { + 'p', 'a', 'p', 0xC3, 0xA0, ' ', + 0xC3, 0xA8, ' ', + 'b', 'e', 'l', 'l', 'o', + 0x00 +}; + +/** + * UTF16 for "papà è bello". + */ +unsigned char test_string_utf16[] = { + 'p', 0x00, 'a', 0x00, 'p', 0x00, 0xE0, 0x00, ' ', 0x00, + 0xE8, 0x00, ' ', 0x00, + 'b', 0x00, 'e', 0x00, 'l', 0x00, 'l', 0x00, 'o', 0x00, + 0x00, 0x00 +}; + +/** + * ISO-8859-1 for "papà è bello". + */ +unsigned char test_string_iso8859_1[] = { + 'p', 'a', 'p', 0xE0, ' ', + 0xE8, ' ', + 'b', 'e', 'l', 'l', 'o', + 0x00 +}; + +/** + * CP1252 for "papà è bello". + */ +unsigned char test_string_cp1252[] = { + 'p', 'a', 'p', 0xE0, ' ', + 0xE8, ' ', + 'b', 'e', 'l', 'l', 'o', + 0x00 +}; + +/** + * Tests that conversion between character sets using the given guac_iconv_read + * and guac_iconv_write implementations matches expectations. + * + * @param reader + * The guac_iconv_read implementation to use to read the input string. + * + * @param in_string + * A pointer to the beginning of the input string. + * + * @param in_length + * The size of the input string in bytes. + * + * @param writer + * The guac_iconv_write implementation to use to write the output string + * (the converted input string). + * + * @param out_string + * A pointer to the beginning of a string which contains the expected + * result of the conversion. + * + * @param out_length + * The size of the expected result in bytes. + */ +static void verify_conversion( guac_iconv_read* reader, unsigned char* in_string, int in_length, guac_iconv_write* writer, unsigned char* out_string, int out_length) { @@ -50,77 +110,74 @@ static void test_conversion( } -void test_guac_iconv() { - - /* UTF8 for "papà è bello" */ - unsigned char test_string_utf8[] = { - 'p', 'a', 'p', 0xC3, 0xA0, ' ', - 0xC3, 0xA8, ' ', - 'b', 'e', 'l', 'l', 'o', - 0x00 - }; - - /* UTF16 for "papà è bello" */ - unsigned char test_string_utf16[] = { - 'p', 0x00, 'a', 0x00, 'p', 0x00, 0xE0, 0x00, ' ', 0x00, - 0xE8, 0x00, ' ', 0x00, - 'b', 0x00, 'e', 0x00, 'l', 0x00, 'l', 0x00, 'o', 0x00, - 0x00, 0x00 - }; - - /* ISO-8859-1 for "papà è bello" */ - unsigned char test_string_iso8859_1[] = { - 'p', 'a', 'p', 0xE0, ' ', - 0xE8, ' ', - 'b', 'e', 'l', 'l', 'o', - 0x00 - }; - - /* CP1252 for "papà è bello" */ - unsigned char test_string_cp1252[] = { - 'p', 'a', 'p', 0xE0, ' ', - 0xE8, ' ', - 'b', 'e', 'l', 'l', 'o', - 0x00 - }; - - /* UTF8 identity */ - test_conversion( +/** + * Tests which verifies conversion of UTF-8 to itself. + */ +void test_iconv__utf8_to_utf8() { + verify_conversion( GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8), GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8)); +} - /* UTF16 identity */ - test_conversion( - GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), - GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16)); - - /* UTF8 to UTF16 */ - test_conversion( +/** + * Tests which verifies conversion of UTF-16 to UTF-8. + */ +void test_iconv__utf8_to_utf16() { + verify_conversion( GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8), GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16)); +} - /* UTF16 to UTF8 */ - test_conversion( +/** + * Tests which verifies conversion of UTF-16 to itself. + */ +void test_iconv__utf16_to_utf16() { + verify_conversion( + GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), + GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16)); +} + +/** + * Tests which verifies conversion of UTF-8 to UTF-16. + */ +void test_iconv__utf16_to_utf8() { + verify_conversion( GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8)); +} - /* UTF16 to ISO-8859-1 */ - test_conversion( +/** + * Tests which verifies conversion of UTF-16 to ISO 8859-1. + */ +void test_iconv__utf16_to_iso8859_1() { + verify_conversion( GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), GUAC_WRITE_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1)); +} - /* UTF16 to CP1252 */ - test_conversion( +/** + * Tests which verifies conversion of UTF-16 to CP1252. + */ +void test_iconv__utf16_to_cp1252() { + verify_conversion( GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), GUAC_WRITE_CP1252, test_string_cp1252, sizeof(test_string_cp1252)); +} - /* CP1252 to UTF8 */ - test_conversion( +/** + * Tests which verifies conversion of CP1252 to UTF-8. + */ +void test_iconv__cp1252_to_utf8() { + verify_conversion( GUAC_READ_CP1252, test_string_cp1252, sizeof(test_string_cp1252), GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8)); +} - /* ISO-8859-1 to UTF8 */ - test_conversion( +/** + * Tests which verifies conversion of ISO 8859-1 to UTF-8. + */ +void test_iconv__iso8859_1_to_utf8() { + verify_conversion( GUAC_READ_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1), GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8)); diff --git a/src/common/tests/rect/clip_and_split.c b/src/common/tests/rect/clip_and_split.c new file mode 100644 index 00000000..e286bce9 --- /dev/null +++ b/src/common/tests/rect/clip_and_split.c @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies that guac_common_rect_clip_and_split() divides a + * rectangle into subrectangles after removing a "hole" rectangle. + */ +void test_rect__clip_and_split() { + + int res; + + guac_common_rect cut; + guac_common_rect min; + guac_common_rect rect; + + guac_common_rect_init(&min, 10, 10, 10, 10); + + /* Clip top */ + guac_common_rect_init(&rect, 10, 5, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(5, cut.y); + CU_ASSERT_EQUAL(10, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(5, rect.height); + + /* Clip bottom */ + guac_common_rect_init(&rect, 10, 15, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(20, cut.y); + CU_ASSERT_EQUAL(10, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(15, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(5, rect.height); + + /* Clip left */ + guac_common_rect_init(&rect, 5, 10, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(5, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Clip right */ + guac_common_rect_init(&rect, 15, 10, 10, 10); + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(20, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(15, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(5, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* + * Test a rectangle which completely covers the hole. + * Clip and split until done. + */ + guac_common_rect_init(&rect, 5, 5, 20, 20); + + /* Clip top */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(5, cut.y); + CU_ASSERT_EQUAL(20, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(5, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(20, rect.width); + CU_ASSERT_EQUAL(15, rect.height); + + /* Clip left */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(5, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(15, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(15, rect.width); + CU_ASSERT_EQUAL(15, rect.height); + + /* Clip bottom */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(1, res); + CU_ASSERT_EQUAL(10, cut.x); + CU_ASSERT_EQUAL(20, cut.y); + CU_ASSERT_EQUAL(15, cut.width); + CU_ASSERT_EQUAL(5, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(15, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Clip right */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(20, cut.x); + CU_ASSERT_EQUAL(10, cut.y); + CU_ASSERT_EQUAL(5, cut.width); + CU_ASSERT_EQUAL(10, cut.height); + + CU_ASSERT_EQUAL(10, rect.x); + CU_ASSERT_EQUAL(10, rect.y); + CU_ASSERT_EQUAL(10, rect.width); + CU_ASSERT_EQUAL(10, rect.height); + + /* Make sure nothing is left to do */ + res = guac_common_rect_clip_and_split(&rect, &min, &cut); + CU_ASSERT_EQUAL(0, res); + +} + diff --git a/tests/test_libguac.c b/src/common/tests/rect/constrain.c similarity index 60% rename from tests/test_libguac.c rename to src/common/tests/rect/constrain.c index 06223ce9..793aac22 100644 --- a/tests/test_libguac.c +++ b/src/common/tests/rect/constrain.c @@ -17,31 +17,27 @@ * under the License. */ -#include "config.h" +#include "common/rect.h" -#include "client/client_suite.h" -#include "common/common_suite.h" -#include "protocol/suite.h" -#include "util/util_suite.h" +#include -#include +/** + * Test which verifies that guac_common_rect_constrain() restricts a given + * rectangle to arbitrary bounds. + */ +void test_rect__constrain() { -int main() { + guac_common_rect max; + guac_common_rect rect; - /* Init registry */ - if (CU_initialize_registry() != CUE_SUCCESS) - return CU_get_error(); + guac_common_rect_init(&rect, -10, -10, 110, 110); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_constrain(&rect, &max); - /* Register suites */ - register_protocol_suite(); - register_client_suite(); - register_util_suite(); - - /* Run tests */ - CU_basic_set_mode(CU_BRM_VERBOSE); - CU_basic_run_tests(); - CU_cleanup_registry(); - return CU_get_error(); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(100, rect.width); + CU_ASSERT_EQUAL(100, rect.height); } diff --git a/src/common/tests/rect/expand_to_grid.c b/src/common/tests/rect/expand_to_grid.c new file mode 100644 index 00000000..beef87d8 --- /dev/null +++ b/src/common/tests/rect/expand_to_grid.c @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies guac_common_rect_expand_to_grid() properly shifts and + * resizes rectangles to fit an NxN grid. + */ +void test_rect__expand_to_grid() { + + int cell_size = 16; + + guac_common_rect max; + guac_common_rect rect; + + /* Simple adjustment */ + guac_common_rect_init(&rect, 0, 0, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + /* Adjustment with moving of rect */ + guac_common_rect_init(&rect, 75, 75, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(max.width - 32, rect.x); + CU_ASSERT_EQUAL(max.height - 32, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + guac_common_rect_init(&rect, -5, -5, 25, 25); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(32, rect.width); + CU_ASSERT_EQUAL(32, rect.height); + + /* Adjustment with moving and clamping of rect */ + guac_common_rect_init(&rect, 0, 0, 25, 15); + guac_common_rect_init(&max, 0, 5, 32, 15); + guac_common_rect_expand_to_grid(cell_size, &rect, &max); + CU_ASSERT_EQUAL(max.x, rect.x); + CU_ASSERT_EQUAL(max.y, rect.y); + CU_ASSERT_EQUAL(max.width, rect.width); + CU_ASSERT_EQUAL(max.height, rect.height); + +} + diff --git a/tests/common/common_suite.h b/src/common/tests/rect/extend.c similarity index 56% rename from tests/common/common_suite.h rename to src/common/tests/rect/extend.c index 60dbe9ae..dc2a0e30 100644 --- a/tests/common/common_suite.h +++ b/src/common/tests/rect/extend.c @@ -17,39 +17,26 @@ * under the License. */ +#include "common/rect.h" -#ifndef _GUAC_TEST_COMMON_SUITE_H -#define _GUAC_TEST_COMMON_SUITE_H +#include /** - * Test suite containing unit tests for the "common" utility library included - * for the sake of simplifying guacamole-server development, but not included - * as part of libguac. - * - * @file common_suite.h + * Test which verifies that guac_common_rect_extend() expands the given + * rectangle as necessary to contain at least the given bounds. */ +void test_rect__extend() { -#include "config.h" + guac_common_rect max; + guac_common_rect rect; -/** - * Registers the common test suite with CUnit. - */ -int register_common_suite(); + guac_common_rect_init(&rect, 10, 10, 90, 90); + guac_common_rect_init(&max, 0, 0, 100, 100); + guac_common_rect_extend(&rect, &max); + CU_ASSERT_EQUAL(0, rect.x); + CU_ASSERT_EQUAL(0, rect.y); + CU_ASSERT_EQUAL(100, rect.width); + CU_ASSERT_EQUAL(100, rect.height); -/** - * Unit test for string utility functions. - */ -void test_guac_string(); - -/** - * Unit test for character conversion functions. - */ -void test_guac_iconv(); - -/** - * Unit test for rectangle calculation functions. - */ -void test_guac_rect(); - -#endif +} diff --git a/tests/client/client_suite.h b/src/common/tests/rect/init.c similarity index 68% rename from tests/client/client_suite.h rename to src/common/tests/rect/init.c index da1bc22b..288cd751 100644 --- a/tests/client/client_suite.h +++ b/src/common/tests/rect/init.c @@ -17,16 +17,23 @@ * under the License. */ +#include "common/rect.h" -#ifndef _GUAC_TEST_CLIENT_SUITE_H -#define _GUAC_TEST_CLIENT_SUITE_H +#include -#include "config.h" +/** + * Test which verifies rectangle initialization via guac_common_rect_init(). + */ +void test_rect__init() { -int register_client_suite(); + guac_common_rect max; -void test_layer_pool(); -void test_buffer_pool(); + guac_common_rect_init(&max, 0, 0, 100, 100); -#endif + CU_ASSERT_EQUAL(0, max.x); + CU_ASSERT_EQUAL(0, max.y); + CU_ASSERT_EQUAL(100, max.width); + CU_ASSERT_EQUAL(100, max.height); + +} diff --git a/src/common/tests/rect/intersects.c b/src/common/tests/rect/intersects.c new file mode 100644 index 00000000..c4802684 --- /dev/null +++ b/src/common/tests/rect/intersects.c @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common/rect.h" + +#include + +/** + * Test which verifies intersection testing via guac_common_rect_intersects(). + */ +void test_rect__intersects() { + + int res; + + guac_common_rect min; + guac_common_rect rect; + + guac_common_rect_init(&min, 10, 10, 10, 10); + + /* Rectangle intersection - empty + * rectangle is outside */ + guac_common_rect_init(&rect, 25, 25, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(0, res); + + /* Rectangle intersection - complete + * rectangle is completely inside */ + guac_common_rect_init(&rect, 11, 11, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects UL */ + guac_common_rect_init(&rect, 8, 8, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - partial + * rectangle intersects LR */ + guac_common_rect_init(&rect, 18, 18, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - complete + * rect intersects along UL but inside */ + guac_common_rect_init(&rect, 10, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects along L but outside */ + guac_common_rect_init(&rect, 5, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - complete + * rectangle intersects along LR but rest is inside */ + guac_common_rect_init(&rect, 15, 15, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(2, res); + + /* Rectangle intersection - partial + * rectangle intersects along R but rest is outside */ + guac_common_rect_init(&rect, 20, 10, 5, 5); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + + /* Rectangle intersection - partial + * rectangle encloses min; which is a partial intersection */ + guac_common_rect_init(&rect, 5, 5, 20, 20); + res = guac_common_rect_intersects(&rect, &min); + CU_ASSERT_EQUAL(1, res); + +} + diff --git a/tests/protocol/suite.h b/src/common/tests/string/count_occurrences.c similarity index 56% rename from tests/protocol/suite.h rename to src/common/tests/string/count_occurrences.c index 01345fac..0c7d01de 100644 --- a/tests/protocol/suite.h +++ b/src/common/tests/string/count_occurrences.c @@ -17,27 +17,17 @@ * under the License. */ +#include "common/string.h" -#ifndef _GUAC_TEST_PROTOCOL_SUITE_H -#define _GUAC_TEST_PROTOCOL_SUITE_H +#include -#include "config.h" - -/* Unicode (UTF-8) strings */ - -#define UTF8_1 "\xe7\x8a\xac" /* One character */ -#define UTF8_2 UTF8_1 "\xf0\x90\xac\x80" /* Two characters */ -#define UTF8_3 UTF8_2 "z" /* Three characters */ -#define UTF8_4 UTF8_3 "\xc3\xa1" /* Four characters */ -#define UTF8_8 UTF8_4 UTF8_4 /* Eight characters */ - -int register_protocol_suite(); - -void test_base64_decode(); -void test_instruction_parse(); -void test_instruction_read(); -void test_instruction_write(); -void test_nest_write(); - -#endif +/** + * Test which verifies that guac_count_occurrences() counts the number of + * occurrences of an arbitrary character within a given string. + */ +void test_string__guac_count_occurrences() { + CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's')); + CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i')); + CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's')); +} diff --git a/tests/common/guac_string.c b/src/common/tests/string/split.c similarity index 79% rename from tests/common/guac_string.c rename to src/common/tests/string/split.c index 543a41b8..36f8f191 100644 --- a/tests/common/guac_string.c +++ b/src/common/tests/string/split.c @@ -17,26 +17,20 @@ * under the License. */ -#include "config.h" - -#include "common_suite.h" #include "common/string.h" +#include + #include -#include -void test_guac_string() { - - char** tokens; - - /* Test occurrence counting */ - CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's')); - CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i')); - CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's')); +/** + * Test which verifies that guac_split() splits a string on occurrences of a + * given character. + */ +void test_string__split() { /* Split test string */ - tokens = guac_split("this is a test string", ' '); - + char** tokens = guac_split("this is a test string", ' '); CU_ASSERT_PTR_NOT_NULL(tokens); /* Check resulting tokens */ @@ -57,7 +51,6 @@ void test_guac_string() { CU_ASSERT_PTR_NULL(tokens[5]); - /* Clean up */ free(tokens[0]); free(tokens[1]); diff --git a/src/libguac/.gitignore b/src/libguac/.gitignore index 95b69b0d..ab580797 100644 --- a/src/libguac/.gitignore +++ b/src/libguac/.gitignore @@ -1,41 +1,9 @@ -# Object code -*.o -*.so -*.lo -*.la +# Auto-generated test runner and binary +_generated_runner.c +test_libguac -# gcov files -*.gcda -*.gcov -*.gcno - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/* -!README -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing +# Test suite output +*.log +*.trs diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index d541df58..2757f706 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -27,6 +27,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac.la +SUBDIRS = . tests libguacincdir = $(includedir)/guacamole diff --git a/tests/Makefile.am b/src/libguac/tests/Makefile.am similarity index 59% rename from tests/Makefile.am rename to src/libguac/tests/Makefile.am index fc1302e5..414a2f45 100644 --- a/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -26,41 +26,51 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -TESTS = test_libguac +# +# Unit tests for libguac +# + check_PROGRAMS = test_libguac +TESTS = $(check_PROGRAMS) -noinst_HEADERS = \ - client/client_suite.h \ - common/common_suite.h \ - protocol/suite.h \ - util/util_suite.h +test_libguac_SOURCES = \ + client/buffer_pool.c \ + client/layer_pool.c \ + parser/append.c \ + parser/read.c \ + pool/next_free.c \ + protocol/base64_decode.c \ + socket/fd_send_instruction.c \ + socket/nested_send_instruction.c \ + unicode/charsize.c \ + unicode/read.c \ + unicode/strlen.c \ + unicode/write.c -test_libguac_SOURCES = \ - test_libguac.c \ - client/client_suite.c \ - client/buffer_pool.c \ - client/layer_pool.c \ - common/common_suite.c \ - common/guac_iconv.c \ - common/guac_string.c \ - common/guac_rect.c \ - protocol/suite.c \ - protocol/base64_decode.c \ - protocol/instruction_parse.c \ - protocol/instruction_read.c \ - protocol/instruction_write.c \ - protocol/nest_write.c \ - util/util_suite.c \ - util/guac_pool.c \ - util/guac_unicode.c test_libguac_CFLAGS = \ -Werror -Wall -pedantic \ - @COMMON_INCLUDE@ \ @LIBGUAC_INCLUDE@ test_libguac_LDADD = \ - @COMMON_LTLIB@ \ @CUNIT_LIBS@ \ @LIBGUAC_LTLIB@ +# +# Autogenerate test runner +# + +GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl +CLEANFILES = _generated_runner.c + +_generated_runner.c: $(test_libguac_SOURCES) + $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + +nodist_test_libguac_SOURCES = \ + _generated_runner.c + +# Use automake's TAP test driver for running any tests +LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh + diff --git a/tests/client/buffer_pool.c b/src/libguac/tests/client/buffer_pool.c similarity index 84% rename from tests/client/buffer_pool.c rename to src/libguac/tests/client/buffer_pool.c index df27f6ef..4f3b3c12 100644 --- a/tests/client/buffer_pool.c +++ b/src/libguac/tests/client/buffer_pool.c @@ -17,20 +17,23 @@ * under the License. */ -#include "config.h" - -#include "client_suite.h" - -#include +#include #include #include -void test_buffer_pool() { +#include + +/** + * Test which verifies that buffers can be allocated and freed using the pool + * of buffers available to each guac_client, and that doing so does not disturb + * the similar pool of layers. + */ +void test_client__buffer_pool() { guac_client* client; int i; - int seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0}; + bool seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = { 0 }; guac_layer* layer; @@ -53,7 +56,7 @@ void test_buffer_pool() { /* This should be a layer we have not seen yet */ CU_ASSERT_FALSE(seen[-layer->index - 1]); - seen[-layer->index - 1] = 1; + seen[-layer->index - 1] = true; guac_client_free_buffer(client, layer); diff --git a/tests/client/layer_pool.c b/src/libguac/tests/client/layer_pool.c similarity index 84% rename from tests/client/layer_pool.c rename to src/libguac/tests/client/layer_pool.c index 8b61c866..f82e84fb 100644 --- a/tests/client/layer_pool.c +++ b/src/libguac/tests/client/layer_pool.c @@ -17,20 +17,23 @@ * under the License. */ -#include "config.h" - -#include "client_suite.h" - -#include +#include #include #include -void test_layer_pool() { +#include + +/** + * Test which verifies that layers can be allocated and freed using the pool + * of layers available to each guac_client, and that doing so does not disturb + * the similar pool of buffers. + */ +void test_client__layer_pool() { guac_client* client; int i; - int seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0}; + bool seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0}; guac_layer* layer; @@ -53,7 +56,7 @@ void test_layer_pool() { /* This should be a layer we have not seen yet */ CU_ASSERT_FALSE(seen[layer->index - 1]); - seen[layer->index - 1] = 1; + seen[layer->index - 1] = true; guac_client_free_layer(client, layer); diff --git a/tests/protocol/instruction_parse.c b/src/libguac/tests/parser/append.c similarity index 89% rename from tests/protocol/instruction_parse.c rename to src/libguac/tests/parser/append.c index ec679854..88515c15 100644 --- a/tests/protocol/instruction_parse.c +++ b/src/libguac/tests/parser/append.c @@ -17,18 +17,18 @@ * under the License. */ -#include "config.h" - -#include "suite.h" +#include +#include #include #include #include -#include -#include - -void test_instruction_parse() { +/** + * Test which verifies that guac_parser correctly parses Guacamole instructions + * from arbitrary blocks of data passed to guac_parser_append(). + */ +void test_parser__append() { /* Allocate parser */ guac_parser* parser = guac_parser_alloc(); @@ -52,6 +52,7 @@ void test_instruction_parse() { } + /* Parse of instruction should be complete */ CU_ASSERT_EQUAL(remaining, 18); CU_ASSERT_EQUAL(parser->state, GUAC_PARSE_COMPLETE); diff --git a/src/libguac/tests/parser/read.c b/src/libguac/tests/parser/read.c new file mode 100644 index 00000000..e3b254c3 --- /dev/null +++ b/src/libguac/tests/parser/read.c @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +/** + * Test string which contains exactly four Unicode characters encoded in UTF-8. + * This particular test string uses several characters which encode to multiple + * bytes in UTF-8. + */ +#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1" + +/** + * Writes a series of Guacamole instructions as raw bytes to the given file + * descriptor. The instructions written correspond to the instructions verified + * by read_expected_instructions(). The given file descriptor is automatically + * closed as a result of calling this function. + * + * @param fd + * The file descriptor to write instructions to. + */ +static void write_instructions(int fd) { + + char test_string[] = "4.test,6.a" UTF8_4 "b," + "5.12345,10.a" UTF8_4 UTF8_4 "c;" + "5.test2,10.hellohello,15.worldworldworld;"; + + char* current = test_string; + int remaining = sizeof(test_string) - 1; + + /* Write all bytes in test string */ + while (remaining > 0) { + + /* Bail out immediately if write fails (test will fail in parent + * process due to failure to read) */ + int written = write(fd, current, remaining); + if (written <= 0) + break; + + current += written; + remaining -= written; + + } + + /* Done writing */ + close(fd); + +} + +/** + * Reads and parses instructions from the given file descriptor using a + * guac_socket and guac_parser, verfying that those instructions match the + * series of Guacamole instructions expected to be written by + * write_instructions(). The given file descriptor is automatically closed as a + * result of calling this function. + * + * @param fd + * The file descriptor to read data from. + */ +static void read_expected_instructions(int fd) { + + /* Open guac socket */ + guac_socket* socket = guac_socket_open(fd); + CU_ASSERT_PTR_NOT_NULL_FATAL(socket); + + /* Allocate parser */ + guac_parser* parser = guac_parser_alloc(); + CU_ASSERT_PTR_NOT_NULL_FATAL(parser); + + /* Read and validate first instruction */ + CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); + CU_ASSERT_STRING_EQUAL(parser->opcode, "test"); + CU_ASSERT_EQUAL_FATAL(parser->argc, 3); + CU_ASSERT_STRING_EQUAL(parser->argv[0], "a" UTF8_4 "b"); + CU_ASSERT_STRING_EQUAL(parser->argv[1], "12345"); + CU_ASSERT_STRING_EQUAL(parser->argv[2], "a" UTF8_4 UTF8_4 "c"); + + /* Read and validate second instruction */ + CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); + CU_ASSERT_STRING_EQUAL(parser->opcode, "test2"); + CU_ASSERT_EQUAL_FATAL(parser->argc, 2); + CU_ASSERT_STRING_EQUAL(parser->argv[0], "hellohello"); + CU_ASSERT_STRING_EQUAL(parser->argv[1], "worldworldworld"); + + /* Done */ + guac_parser_free(parser); + guac_socket_free(socket); + +} + +/** + * Tests that guac_parser_read() correctly reads and parses instructions + * received over a guac_socket. A child process is forked to write a series of + * instructions which are read and verified by the parent process. + */ +void test_parser__read() { + + int fd[2]; + + /* Create pipe */ + CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); + + int read_fd = fd[0]; + int write_fd = fd[1]; + + /* Fork into writer process (child) and reader process (parent) */ + int childpid; + CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1); + + /* Attempt to write a series of instructions within the child process */ + if (childpid == 0) { + close(read_fd); + write_instructions(write_fd); + exit(0); + } + + /* Read and verify the expected instructions within the parent process */ + close(write_fd); + read_expected_instructions(read_fd); + +} + diff --git a/tests/util/guac_pool.c b/src/libguac/tests/pool/next_free.c similarity index 78% rename from tests/util/guac_pool.c rename to src/libguac/tests/pool/next_free.c index e24d8fc3..e8bd945e 100644 --- a/tests/util/guac_pool.c +++ b/src/libguac/tests/pool/next_free.c @@ -17,20 +17,21 @@ * under the License. */ -#include "config.h" - -#include "util_suite.h" - -#include +#include #include -#define UNSEEN 0 -#define SEEN_PHASE_1 1 -#define SEEN_PHASE_2 2 - +/** + * The number of unique integers to provide through the guac_pool instance + * being tested. + */ #define POOL_SIZE 128 -void test_guac_pool() { +/** + * Test which verifies that guac_pool provides access to a given number of + * unique integers, never repeating a retrieved integer until that integer + * is returned to the pool. + */ +void test_pool__next_free() { guac_pool* pool; @@ -53,8 +54,8 @@ void test_guac_pool() { CU_ASSERT_FATAL(value < POOL_SIZE); /* This should be an integer we have not seen yet */ - CU_ASSERT_EQUAL(UNSEEN, seen[value]); - seen[value] = SEEN_PHASE_1; + CU_ASSERT_EQUAL(0, seen[value]); + seen[value]++; /* Return value to pool */ guac_pool_free_int(pool, value); @@ -71,9 +72,9 @@ void test_guac_pool() { CU_ASSERT_FATAL(value >= 0); CU_ASSERT_FATAL(value < POOL_SIZE); - /* This should be an integer we have seen already */ - CU_ASSERT_EQUAL(SEEN_PHASE_1, seen[value]); - seen[value] = SEEN_PHASE_2; + /* This should be an integer we have seen only once */ + CU_ASSERT_EQUAL(1, seen[value]); + seen[value]++; } diff --git a/tests/protocol/base64_decode.c b/src/libguac/tests/protocol/base64_decode.c similarity index 90% rename from tests/protocol/base64_decode.c rename to src/libguac/tests/protocol/base64_decode.c index 9f8b7b7c..60c8a032 100644 --- a/tests/protocol/base64_decode.c +++ b/src/libguac/tests/protocol/base64_decode.c @@ -17,18 +17,14 @@ * under the License. */ -#include "config.h" - -#include "suite.h" - -#include -#include -#include - -#include +#include #include -void test_base64_decode() { +/** + * Tests that libguac's in-place base64 decoding function properly decodes + * valid base64 and fails for invalid base64. + */ +void test_protocol__decode_base64() { /* Test strings */ char test_HELLO[] = "SEVMTE8="; diff --git a/src/libguac/tests/socket/fd_send_instruction.c b/src/libguac/tests/socket/fd_send_instruction.c new file mode 100644 index 00000000..5a162d61 --- /dev/null +++ b/src/libguac/tests/socket/fd_send_instruction.c @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +#include +#include + +/** + * Test string which contains exactly four Unicode characters encoded in UTF-8. + * This particular test string uses several characters which encode to multiple + * bytes in UTF-8. + */ +#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1" + +/** + * Writes a series of Guacamole instructions using a normal guac_socket + * wrapping the given file descriptor. The instructions written correspond to + * the instructions verified by read_expected_instructions(). The given file + * descriptor is automatically closed as a result of calling this function. + * + * @param fd + * The file descriptor to write instructions to. + */ +static void write_instructions(int fd) { + + /* Open guac socket */ + guac_socket* socket = guac_socket_open(fd); + + /* Write nothing if socket cannot be allocated (test will fail in parent + * process due to failure to read) */ + if (socket == NULL) { + close(fd); + return; + } + + /* Write instructions */ + guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c"); + guac_protocol_send_sync(socket, 12345); + guac_socket_flush(socket); + + /* Close and free socket */ + guac_socket_free(socket); + +} + +/** + * Reads raw bytes from the given file descriptor until no further bytes + * remain, verfying that those bytes represent the series of Guacamole + * instructions expected to be written by write_instructions(). The given + * file descriptor is automatically closed as a result of calling this + * function. + * + * @param fd + * The file descriptor to read data from. + */ +static void read_expected_instructions(int fd) { + + char expected[] = + "4.name,11.a" UTF8_4 "b" UTF8_4 "c;" + "4.sync,5.12345;"; + + int numread; + char buffer[1024]; + int offset = 0; + + /* Read everything available into buffer */ + while ((numread = read(fd, &(buffer[offset]), + sizeof(buffer) - offset)) > 0) { + offset += numread; + } + + /* Verify length of read data */ + CU_ASSERT_EQUAL(offset, strlen(expected)); + + /* Add NULL terminator */ + buffer[offset] = '\0'; + + /* Read value should be equal to expected value */ + CU_ASSERT_STRING_EQUAL(buffer, expected); + + /* File descriptor is no longer needed */ + close(fd); + +} + +/** + * Tests that the file descriptor implementation of guac_socket properly + * implements writing of instructions. A child process is forked to write a + * series of instructions which are read and verified by the parent process. + */ +void test_socket__fd_send_instruction() { + + int fd[2]; + + /* Create pipe */ + CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); + + int read_fd = fd[0]; + int write_fd = fd[1]; + + /* Fork into writer process (child) and reader process (parent) */ + int childpid; + CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1); + + /* Attempt to write a series of instructions within the child process */ + if (childpid == 0) { + close(read_fd); + write_instructions(write_fd); + exit(0); + } + + /* Read and verify the expected instructions within the parent process */ + close(write_fd); + read_expected_instructions(read_fd); + +} + diff --git a/src/libguac/tests/socket/nested_send_instruction.c b/src/libguac/tests/socket/nested_send_instruction.c new file mode 100644 index 00000000..db29e2b2 --- /dev/null +++ b/src/libguac/tests/socket/nested_send_instruction.c @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +#include +#include + +/** + * Test string which contains exactly four Unicode characters encoded in UTF-8. + * This particular test string uses several characters which encode to multiple + * bytes in UTF-8. + */ +#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1" + +/** + * Writes a series of Guacamole instructions using a nested guac_socket + * wrapping another guac_socket which writes to the given file descriptor. The + * instructions written correspond to the instructions verified by + * read_expected_instructions(). The given file descriptor is automatically + * closed as a result of calling this function. + * + * @param fd + * The file descriptor to write instructions to. + */ +static void write_instructions(int fd) { + + /* Open guac socket */ + guac_socket* socket = guac_socket_open(fd); + + /* Write nothing if socket cannot be allocated (test will fail in parent + * process due to failure to read) */ + if (socket == NULL) { + close(fd); + return; + } + + /* Nest socket */ + guac_socket* nested_socket = guac_socket_nest(socket, 123); + + /* Write nothing if nested socket cannot be allocated (test will fail in + * parent process due to failure to read) */ + if (socket == NULL) { + guac_socket_free(socket); + return; + } + + /* Write instructions */ + guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c"); + guac_protocol_send_sync(nested_socket, 12345); + + /* Close and free sockets */ + guac_socket_free(nested_socket); + guac_socket_free(socket); + +} + +/** + * Reads raw bytes from the given file descriptor until no further bytes + * remain, verfying that those bytes represent the series of Guacamole + * instructions expected to be written by write_instructions(). The given + * file descriptor is automatically closed as a result of calling this + * function. + * + * @param fd + * The file descriptor to read data from. + */ +static void read_expected_instructions(int fd) { + + char expected[] = + "4.nest,3.123,37." + "4.name,11.a" UTF8_4 "b" UTF8_4 "c;" + "4.sync,5.12345;" + ";"; + + int numread; + char buffer[1024]; + int offset = 0; + + /* Read everything available into buffer */ + while ((numread = read(fd, &(buffer[offset]), + sizeof(buffer) - offset)) > 0) { + offset += numread; + } + + /* Verify length of read data */ + CU_ASSERT_EQUAL(offset, strlen(expected)); + + /* Add NULL terminator */ + buffer[offset] = '\0'; + + /* Read value should be equal to expected value */ + CU_ASSERT_STRING_EQUAL(buffer, expected); + + /* File descriptor is no longer needed */ + close(fd); + +} + +/** + * Tests that the nested socket implementation of guac_socket properly + * implements writing of instructions. A child process is forked to write a + * series of instructions which are read and verified by the parent process. + */ +void test_socket__nested_send_instruction() { + + int fd[2]; + + /* Create pipe */ + CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); + + int read_fd = fd[0]; + int write_fd = fd[1]; + + /* Fork into writer process (child) and reader process (parent) */ + int childpid; + CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1); + + /* Attempt to write a series of instructions within the child process */ + if (childpid == 0) { + close(read_fd); + write_instructions(write_fd); + exit(0); + } + + /* Read and verify the expected instructions within the parent process */ + close(write_fd); + read_expected_instructions(read_fd); + +} + diff --git a/src/libguac/tests/unicode/charsize.c b/src/libguac/tests/unicode/charsize.c new file mode 100644 index 00000000..e636155f --- /dev/null +++ b/src/libguac/tests/unicode/charsize.c @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +/** + * Test which verifies that guac_utf8_charsize() correctly determines the + * length of UTF-8 characters from the leading byte of that character. + */ +void test_unicode__utf8_charsize() { + CU_ASSERT_EQUAL(1, guac_utf8_charsize('g')); + CU_ASSERT_EQUAL(2, guac_utf8_charsize('\xC4')); + CU_ASSERT_EQUAL(3, guac_utf8_charsize('\xE7')); + CU_ASSERT_EQUAL(4, guac_utf8_charsize('\xF0')); +} + diff --git a/src/libguac/tests/unicode/read.c b/src/libguac/tests/unicode/read.c new file mode 100644 index 00000000..230a721c --- /dev/null +++ b/src/libguac/tests/unicode/read.c @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +/** + * Test which verifies that guac_utf8_read() properly parses UTF-8. + */ +void test_unicode__utf8_read() { + + int codepoint; + + char buffer[16] = + /* U+0065 */ "\x65" + /* U+0654 */ "\xD9\x94" + /* U+0876 */ "\xE0\xA1\xB6" + /* U+12345 */ "\xF0\x92\x8D\x85"; + + CU_ASSERT_EQUAL(1, guac_utf8_read(&(buffer[0]), 10, &codepoint)); + CU_ASSERT_EQUAL(0x0065, codepoint); + + CU_ASSERT_EQUAL(2, guac_utf8_read(&(buffer[1]), 9, &codepoint)); + CU_ASSERT_EQUAL(0x0654, codepoint); + + CU_ASSERT_EQUAL(3, guac_utf8_read(&(buffer[3]), 7, &codepoint)); + CU_ASSERT_EQUAL(0x0876, codepoint); + + CU_ASSERT_EQUAL(4, guac_utf8_read(&(buffer[6]), 4, &codepoint)); + CU_ASSERT_EQUAL(0x12345, codepoint); + + CU_ASSERT_EQUAL(0, guac_utf8_read(&(buffer[10]), 0, &codepoint)); + CU_ASSERT_EQUAL(0x12345, codepoint); + +} + diff --git a/tests/util/util_suite.h b/src/libguac/tests/unicode/strlen.c similarity index 55% rename from tests/util/util_suite.h rename to src/libguac/tests/unicode/strlen.c index 3e3253b0..995a5038 100644 --- a/tests/util/util_suite.h +++ b/src/libguac/tests/unicode/strlen.c @@ -17,19 +17,8 @@ * under the License. */ - -#ifndef _GUAC_TEST_UTIL_SUITE_H -#define _GUAC_TEST_UTIL_SUITE_H - -/** - * Test suite containing unit tests for utility functions built into libguac. - * These utility functions are included for convenience rather as integral - * requirements of the core. - * - * @file util_suite.h - */ - -#include "config.h" +#include +#include /** * A single Unicode character encoded as one byte with UTF-8. @@ -52,24 +41,19 @@ #define UTF8_4b "\xf0\x90\x84\xa3" /** - * Registers the utility test suite with CUnit. + * Test which verifies that guac_utf8_strlen() properly calculates the length + * of UTF-8 strings. */ -int register_util_suite(); - -/** - * Unit test for the guac_pool structure and related functions. The guac_pool - * structure provides a consistent source of pooled integers. This unit test - * checks that the associated functions behave as documented (returning - * integers in the proper order, allocating new integers as necessary, etc.). - */ -void test_guac_pool(); - -/** - * Unit test for libguac's Unicode convenience functions. This test checks that - * the functions provided for determining string length, character length, and - * for reading and writing UTF-8 behave as specified in the documentation. - */ -void test_guac_unicode(); - -#endif +void test_unicode__utf8_strlen() { + CU_ASSERT_EQUAL(0, guac_utf8_strlen("")); + CU_ASSERT_EQUAL(1, guac_utf8_strlen(UTF8_4b)); + CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_4b UTF8_1b)); + CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_2b UTF8_3b)); + CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_1b UTF8_3b UTF8_4b)); + CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_2b UTF8_1b UTF8_3b)); + CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_4b UTF8_2b UTF8_1b)); + CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_3b UTF8_4b UTF8_2b)); + CU_ASSERT_EQUAL(5, guac_utf8_strlen("hello")); + CU_ASSERT_EQUAL(9, guac_utf8_strlen("guacamole")); +} diff --git a/tests/util/guac_unicode.c b/src/libguac/tests/unicode/write.c similarity index 50% rename from tests/util/guac_unicode.c rename to src/libguac/tests/unicode/write.c index 62f58876..8480086d 100644 --- a/tests/util/guac_unicode.c +++ b/src/libguac/tests/unicode/write.c @@ -17,36 +17,17 @@ * under the License. */ -#include "config.h" - -#include "util_suite.h" - -#include +#include #include -void test_guac_unicode() { +/** + * Test which verifies that guac_utf8_write() properly encodes Unicode + * codepoints as UTF-8. + */ +void test_unicode__utf8_write() { - int codepoint; char buffer[16]; - /* Test character length */ - CU_ASSERT_EQUAL(1, guac_utf8_charsize(UTF8_1b[0])); - CU_ASSERT_EQUAL(2, guac_utf8_charsize(UTF8_2b[0])); - CU_ASSERT_EQUAL(3, guac_utf8_charsize(UTF8_3b[0])); - CU_ASSERT_EQUAL(4, guac_utf8_charsize(UTF8_4b[0])); - - /* Test string length */ - CU_ASSERT_EQUAL(0, guac_utf8_strlen("")); - CU_ASSERT_EQUAL(1, guac_utf8_strlen(UTF8_4b)); - CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_4b UTF8_1b)); - CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_2b UTF8_3b)); - CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_1b UTF8_3b UTF8_4b)); - CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_2b UTF8_1b UTF8_3b)); - CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_4b UTF8_2b UTF8_1b)); - CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_3b UTF8_4b UTF8_2b)); - CU_ASSERT_EQUAL(5, guac_utf8_strlen("hello")); - CU_ASSERT_EQUAL(9, guac_utf8_strlen("guacamole")); - /* Test writes */ CU_ASSERT_EQUAL(1, guac_utf8_write(0x00065, &(buffer[0]), 10)); CU_ASSERT_EQUAL(2, guac_utf8_write(0x00654, &(buffer[1]), 9)); @@ -60,22 +41,5 @@ void test_guac_unicode() { CU_ASSERT(memcmp("\xE0\xA1\xB6", &(buffer[3]), 3) == 0); /* U+0876 */ CU_ASSERT(memcmp("\xF0\x92\x8D\x85", &(buffer[6]), 4) == 0); /* U+12345 */ - /* Test reads */ - - CU_ASSERT_EQUAL(1, guac_utf8_read(&(buffer[0]), 10, &codepoint)); - CU_ASSERT_EQUAL(0x0065, codepoint); - - CU_ASSERT_EQUAL(2, guac_utf8_read(&(buffer[1]), 9, &codepoint)); - CU_ASSERT_EQUAL(0x0654, codepoint); - - CU_ASSERT_EQUAL(3, guac_utf8_read(&(buffer[3]), 7, &codepoint)); - CU_ASSERT_EQUAL(0x0876, codepoint); - - CU_ASSERT_EQUAL(4, guac_utf8_read(&(buffer[6]), 4, &codepoint)); - CU_ASSERT_EQUAL(0x12345, codepoint); - - CU_ASSERT_EQUAL(0, guac_utf8_read(&(buffer[10]), 0, &codepoint)); - CU_ASSERT_EQUAL(0x12345, codepoint); - } diff --git a/tests/client/client_suite.c b/tests/client/client_suite.c deleted file mode 100644 index 18bba6bb..00000000 --- a/tests/client/client_suite.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "client_suite.h" - -#include - -int client_suite_init() { - return 0; -} - -int client_suite_cleanup() { - return 0; -} - -int register_client_suite() { - - /* Add client test suite */ - CU_pSuite suite = CU_add_suite("client", - client_suite_init, client_suite_cleanup); - if (suite == NULL) { - CU_cleanup_registry(); - return CU_get_error(); - } - - /* Add tests */ - if ( - CU_add_test(suite, "layer-pool", test_layer_pool) == NULL - || CU_add_test(suite, "buffer-pool", test_buffer_pool) == NULL - ) { - CU_cleanup_registry(); - return CU_get_error(); - } - - return 0; - -} - diff --git a/tests/common/common_suite.c b/tests/common/common_suite.c deleted file mode 100644 index 58ca7993..00000000 --- a/tests/common/common_suite.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "common_suite.h" - -#include - -int common_suite_init() { - return 0; -} - -int common_suite_cleanup() { - return 0; -} - -int register_common_suite() { - - /* Add common test suite */ - CU_pSuite suite = CU_add_suite("common", - common_suite_init, common_suite_cleanup); - if (suite == NULL) { - CU_cleanup_registry(); - return CU_get_error(); - } - - /* Add tests */ - if ( - CU_add_test(suite, "guac-iconv", test_guac_iconv) == NULL - || CU_add_test(suite, "guac-string", test_guac_string) == NULL - || CU_add_test(suite, "guac-rect", test_guac_rect) == NULL - ) { - CU_cleanup_registry(); - return CU_get_error(); - } - - return 0; - -} - diff --git a/tests/common/guac_rect.c b/tests/common/guac_rect.c deleted file mode 100644 index a45ee385..00000000 --- a/tests/common/guac_rect.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "common_suite.h" -#include "common/rect.h" - -#include -#include -#include - -void test_guac_rect() { - - guac_common_rect max; - - /* - * Test init method - */ - guac_common_rect_init(&max, 0, 0, 100, 100); - CU_ASSERT_EQUAL(0, max.x); - CU_ASSERT_EQUAL(0, max.y); - CU_ASSERT_EQUAL(100, max.width); - CU_ASSERT_EQUAL(100, max.height); - - /* - * Test constrain method - */ - guac_common_rect rect; - guac_common_rect_init(&rect, -10, -10, 110, 110); - guac_common_rect_init(&max, 0, 0, 100, 100); - guac_common_rect_constrain(&rect, &max); - CU_ASSERT_EQUAL(0, rect.x); - CU_ASSERT_EQUAL(0, rect.y); - CU_ASSERT_EQUAL(100, rect.width); - CU_ASSERT_EQUAL(100, rect.height); - - /* - * Test extend method - */ - guac_common_rect_init(&rect, 10, 10, 90, 90); - guac_common_rect_init(&max, 0, 0, 100, 100); - guac_common_rect_extend(&rect, &max); - CU_ASSERT_EQUAL(0, rect.x); - CU_ASSERT_EQUAL(0, rect.y); - CU_ASSERT_EQUAL(100, rect.width); - CU_ASSERT_EQUAL(100, rect.height); - - /* - * Test adjust method - */ - int cell_size = 16; - - /* Simple adjustment */ - guac_common_rect_init(&rect, 0, 0, 25, 25); - guac_common_rect_init(&max, 0, 0, 100, 100); - guac_common_rect_expand_to_grid(cell_size, &rect, &max); - CU_ASSERT_EQUAL(0, rect.x); - CU_ASSERT_EQUAL(0, rect.y); - CU_ASSERT_EQUAL(32, rect.width); - CU_ASSERT_EQUAL(32, rect.height); - - /* Adjustment with moving of rect */ - guac_common_rect_init(&rect, 75, 75, 25, 25); - guac_common_rect_init(&max, 0, 0, 100, 100); - guac_common_rect_expand_to_grid(cell_size, &rect, &max); - CU_ASSERT_EQUAL(max.width - 32, rect.x); - CU_ASSERT_EQUAL(max.height - 32, rect.y); - CU_ASSERT_EQUAL(32, rect.width); - CU_ASSERT_EQUAL(32, rect.height); - - guac_common_rect_init(&rect, -5, -5, 25, 25); - guac_common_rect_init(&max, 0, 0, 100, 100); - guac_common_rect_expand_to_grid(cell_size, &rect, &max); - CU_ASSERT_EQUAL(0, rect.x); - CU_ASSERT_EQUAL(0, rect.y); - CU_ASSERT_EQUAL(32, rect.width); - CU_ASSERT_EQUAL(32, rect.height); - - /* Adjustment with moving and clamping of rect */ - guac_common_rect_init(&rect, 0, 0, 25, 15); - guac_common_rect_init(&max, 0, 5, 32, 15); - guac_common_rect_expand_to_grid(cell_size, &rect, &max); - CU_ASSERT_EQUAL(max.x, rect.x); - CU_ASSERT_EQUAL(max.y, rect.y); - CU_ASSERT_EQUAL(max.width, rect.width); - CU_ASSERT_EQUAL(max.height, rect.height); - - /* - * Rectangle intersection tests - */ - guac_common_rect min; - guac_common_rect_init(&min, 10, 10, 10, 10); - - /* Rectangle intersection - empty - * rectangle is outside */ - guac_common_rect_init(&rect, 25, 25, 5, 5); - int res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(0, res); - - /* Rectangle intersection - complete - * rectangle is completely inside */ - guac_common_rect_init(&rect, 11, 11, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(2, res); - - /* Rectangle intersection - partial - * rectangle intersects UL */ - guac_common_rect_init(&rect, 8, 8, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(1, res); - - /* Rectangle intersection - partial - * rectangle intersects LR */ - guac_common_rect_init(&rect, 18, 18, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(1, res); - - /* Rectangle intersection - complete - * rect intersects along UL but inside */ - guac_common_rect_init(&rect, 10, 10, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(2, res); - - /* Rectangle intersection - partial - * rectangle intersects along L but outside */ - guac_common_rect_init(&rect, 5, 10, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(1, res); - - /* Rectangle intersection - complete - * rectangle intersects along LR but rest is inside */ - guac_common_rect_init(&rect, 15, 15, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(2, res); - - /* Rectangle intersection - partial - * rectangle intersects along R but rest is outside */ - guac_common_rect_init(&rect, 20, 10, 5, 5); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(1, res); - - /* Rectangle intersection - partial - * rectangle encloses min; which is a partial intersection */ - guac_common_rect_init(&rect, 5, 5, 20, 20); - res = guac_common_rect_intersects(&rect, &min); - CU_ASSERT_EQUAL(1, res); - - /* - * Basic test of clip and split method - */ - guac_common_rect_init(&min, 10, 10, 10, 10); - guac_common_rect cut; - - /* Clip top */ - guac_common_rect_init(&rect, 10, 5, 10, 10); - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(10, cut.x); - CU_ASSERT_EQUAL(5, cut.y); - CU_ASSERT_EQUAL(10, cut.width); - CU_ASSERT_EQUAL(5, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(10, rect.width); - CU_ASSERT_EQUAL(5, rect.height); - - /* Clip bottom */ - guac_common_rect_init(&rect, 10, 15, 10, 10); - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(10, cut.x); - CU_ASSERT_EQUAL(20, cut.y); - CU_ASSERT_EQUAL(10, cut.width); - CU_ASSERT_EQUAL(5, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(15, rect.y); - CU_ASSERT_EQUAL(10, rect.width); - CU_ASSERT_EQUAL(5, rect.height); - - /* Clip left */ - guac_common_rect_init(&rect, 5, 10, 10, 10); - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(5, cut.x); - CU_ASSERT_EQUAL(10, cut.y); - CU_ASSERT_EQUAL(5, cut.width); - CU_ASSERT_EQUAL(10, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(5, rect.width); - CU_ASSERT_EQUAL(10, rect.height); - - /* Clip right */ - guac_common_rect_init(&rect, 15, 10, 10, 10); - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(20, cut.x); - CU_ASSERT_EQUAL(10, cut.y); - CU_ASSERT_EQUAL(5, cut.width); - CU_ASSERT_EQUAL(10, cut.height); - - CU_ASSERT_EQUAL(15, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(5, rect.width); - CU_ASSERT_EQUAL(10, rect.height); - - /* - * Test a rectangle which completely covers the hole. - * Clip and split until done. - */ - guac_common_rect_init(&rect, 5, 5, 20, 20); - - /* Clip top */ - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(5, cut.x); - CU_ASSERT_EQUAL(5, cut.y); - CU_ASSERT_EQUAL(20, cut.width); - CU_ASSERT_EQUAL(5, cut.height); - - CU_ASSERT_EQUAL(5, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(20, rect.width); - CU_ASSERT_EQUAL(15, rect.height); - - /* Clip left */ - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(5, cut.x); - CU_ASSERT_EQUAL(10, cut.y); - CU_ASSERT_EQUAL(5, cut.width); - CU_ASSERT_EQUAL(15, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(15, rect.width); - CU_ASSERT_EQUAL(15, rect.height); - - /* Clip bottom */ - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(1, res); - CU_ASSERT_EQUAL(10, cut.x); - CU_ASSERT_EQUAL(20, cut.y); - CU_ASSERT_EQUAL(15, cut.width); - CU_ASSERT_EQUAL(5, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(15, rect.width); - CU_ASSERT_EQUAL(10, rect.height); - - /* Clip right */ - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(20, cut.x); - CU_ASSERT_EQUAL(10, cut.y); - CU_ASSERT_EQUAL(5, cut.width); - CU_ASSERT_EQUAL(10, cut.height); - - CU_ASSERT_EQUAL(10, rect.x); - CU_ASSERT_EQUAL(10, rect.y); - CU_ASSERT_EQUAL(10, rect.width); - CU_ASSERT_EQUAL(10, rect.height); - - /* Make sure nothing is left to do */ - res = guac_common_rect_clip_and_split(&rect, &min, &cut); - CU_ASSERT_EQUAL(0, res); - -} - diff --git a/tests/protocol/instruction_read.c b/tests/protocol/instruction_read.c deleted file mode 100644 index 930b5b5e..00000000 --- a/tests/protocol/instruction_read.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "suite.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -void test_instruction_read() { - - int rfd, wfd; - int fd[2], childpid; - - char test_string[] = "4.test,6.a" UTF8_4 "b," - "5.12345,10.a" UTF8_8 "c;" - "5.test2,10.hellohello,15.worldworldworld;"; - - /* Create pipe */ - CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); - - /* File descriptors */ - rfd = fd[0]; - wfd = fd[1]; - - /* Fork */ - if ((childpid = fork()) == -1) { - /* ERROR */ - perror("fork"); - return; - } - - /* Child (pipe writer) */ - if (childpid == 0) { - close(rfd); - CU_ASSERT_EQUAL( - write(wfd, test_string, sizeof(test_string)), - sizeof(test_string) - ); - exit(0); - } - - /* Parent (unit test) */ - else { - - guac_socket* socket; - guac_parser* parser; - - close(wfd); - - /* Open guac socket */ - socket = guac_socket_open(rfd); - CU_ASSERT_PTR_NOT_NULL_FATAL(socket); - - /* Allocate parser */ - parser = guac_parser_alloc(); - CU_ASSERT_PTR_NOT_NULL_FATAL(parser); - - /* Read instruction */ - CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); - - /* Validate contents */ - CU_ASSERT_STRING_EQUAL(parser->opcode, "test"); - CU_ASSERT_EQUAL_FATAL(parser->argc, 3); - CU_ASSERT_STRING_EQUAL(parser->argv[0], "a" UTF8_4 "b"); - CU_ASSERT_STRING_EQUAL(parser->argv[1], "12345"); - CU_ASSERT_STRING_EQUAL(parser->argv[2], "a" UTF8_8 "c"); - - /* Read another instruction */ - CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0); - - /* Validate contents */ - CU_ASSERT_STRING_EQUAL(parser->opcode, "test2"); - CU_ASSERT_EQUAL_FATAL(parser->argc, 2); - CU_ASSERT_STRING_EQUAL(parser->argv[0], "hellohello"); - CU_ASSERT_STRING_EQUAL(parser->argv[1], "worldworldworld"); - - guac_parser_free(parser); - guac_socket_free(socket); - - } - -} - diff --git a/tests/protocol/instruction_write.c b/tests/protocol/instruction_write.c deleted file mode 100644 index 8e12dfc1..00000000 --- a/tests/protocol/instruction_write.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "suite.h" - -#include -#include -#include - -#include -#include -#include -#include - -void test_instruction_write() { - - int rfd, wfd; - int fd[2], childpid; - - /* Create pipe */ - CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); - - /* File descriptors */ - rfd = fd[0]; - wfd = fd[1]; - - /* Fork */ - if ((childpid = fork()) == -1) { - /* ERROR */ - perror("fork"); - return; - } - - /* Child (pipe writer) */ - if (childpid == 0) { - - guac_socket* socket; - - close(rfd); - - /* Open guac socket */ - socket = guac_socket_open(wfd); - - /* Write instruction */ - guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c"); - guac_protocol_send_sync(socket, 12345); - guac_socket_flush(socket); - - guac_socket_free(socket); - exit(0); - } - - /* Parent (unit test) */ - else { - - char expected[] = - "4.name,11.a" UTF8_4 "b" UTF8_4 "c;" - "4.sync,5.12345;"; - - int numread; - char buffer[1024]; - int offset = 0; - - close(wfd); - - /* Read everything available into buffer */ - while ((numread = - read(rfd, - &(buffer[offset]), - sizeof(buffer)-offset)) != 0) { - offset += numread; - } - - /* Add NULL terminator */ - buffer[offset] = '\0'; - - /* Read value should be equal to expected value */ - CU_ASSERT_STRING_EQUAL(buffer, expected); - - } - -} - diff --git a/tests/protocol/nest_write.c b/tests/protocol/nest_write.c deleted file mode 100644 index e1b885b2..00000000 --- a/tests/protocol/nest_write.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "suite.h" - -#include -#include -#include - -#include -#include -#include -#include - -void test_nest_write() { - - int rfd, wfd; - int fd[2], childpid; - - /* Create pipe */ - CU_ASSERT_EQUAL_FATAL(pipe(fd), 0); - - /* File descriptors */ - rfd = fd[0]; - wfd = fd[1]; - - /* Fork */ - if ((childpid = fork()) == -1) { - /* ERROR */ - perror("fork"); - return; - } - - /* Child (pipe writer) */ - if (childpid == 0) { - - guac_socket* nested_socket; - guac_socket* socket; - - close(rfd); - - /* Open guac socket */ - socket = guac_socket_open(wfd); - - /* Nest socket */ - nested_socket = guac_socket_nest(socket, 0); - - /* Write instruction */ - guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c"); - guac_protocol_send_sync(nested_socket, 12345); - guac_socket_flush(nested_socket); - guac_socket_flush(socket); - - guac_socket_free(nested_socket); - guac_socket_free(socket); - exit(0); - } - - /* Parent (unit test) */ - else { - - char expected[] = - "4.nest,1.0,37." - "4.name,11.a" UTF8_4 "b" UTF8_4 "c;" - "4.sync,5.12345;" - ";"; - - int numread; - char buffer[1024]; - int offset = 0; - - close(wfd); - - /* Read everything available into buffer */ - while ((numread = - read(rfd, - &(buffer[offset]), - sizeof(buffer)-offset)) != 0) { - offset += numread; - } - - /* Add NULL terminator */ - buffer[offset] = '\0'; - - /* Read value should be equal to expected value */ - CU_ASSERT_STRING_EQUAL(buffer, expected); - - } - -} - diff --git a/tests/protocol/suite.c b/tests/protocol/suite.c deleted file mode 100644 index 14cfc406..00000000 --- a/tests/protocol/suite.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "suite.h" - -#include - -int protocol_suite_init() { - return 0; -} - -int protocol_suite_cleanup() { - return 0; -} - -int register_protocol_suite() { - - /* Add protocol test suite */ - CU_pSuite suite = CU_add_suite("protocol", - protocol_suite_init, protocol_suite_cleanup); - if (suite == NULL) { - CU_cleanup_registry(); - return CU_get_error(); - } - - /* Add tests */ - if ( - CU_add_test(suite, "base64-decode", test_base64_decode) == NULL - || CU_add_test(suite, "instruction-parse", test_instruction_parse) == NULL - || CU_add_test(suite, "instruction-read", test_instruction_read) == NULL - || CU_add_test(suite, "instruction-write", test_instruction_write) == NULL - || CU_add_test(suite, "nest-write", test_nest_write) == NULL - ) { - CU_cleanup_registry(); - return CU_get_error(); - } - - return 0; - -} - diff --git a/tests/util/util_suite.c b/tests/util/util_suite.c deleted file mode 100644 index 7213a0f2..00000000 --- a/tests/util/util_suite.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "util_suite.h" - -#include - -int util_suite_init() { - return 0; -} - -int util_suite_cleanup() { - return 0; -} - -int register_util_suite() { - - /* Add util test suite */ - CU_pSuite suite = CU_add_suite("util", - util_suite_init, util_suite_cleanup); - if (suite == NULL) { - CU_cleanup_registry(); - return CU_get_error(); - } - - /* Add tests */ - if ( - CU_add_test(suite, "guac-pool", test_guac_pool) == NULL - || CU_add_test(suite, "guac-unicode", test_guac_unicode) == NULL - ) { - CU_cleanup_registry(); - return CU_get_error(); - } - - return 0; - -} - From 9486ec7cc491514ba2acd59ccf286edd32d32ec2 Mon Sep 17 00:00:00 2001 From: Andrin Date: Tue, 20 Nov 2018 14:10:36 +0100 Subject: [PATCH 068/175] GUACAMOLE-354: Add Swiss-German keymap for RDP --- src/protocols/rdp/Makefile.am | 1 + src/protocols/rdp/keymaps/de_ch_qwertz.keymap | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/protocols/rdp/keymaps/de_ch_qwertz.keymap diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 670359fc..05a7a3ab 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -258,6 +258,7 @@ rdp_keymaps = \ $(srcdir)/keymaps/base.keymap \ $(srcdir)/keymaps/failsafe.keymap \ $(srcdir)/keymaps/de_de_qwertz.keymap \ + $(srcdir)/keymaps/de_ch_qwertz.keymap \ $(srcdir)/keymaps/en_gb_qwerty.keymap \ $(srcdir)/keymaps/en_us_qwerty.keymap \ $(srcdir)/keymaps/es_es_qwerty.keymap \ diff --git a/src/protocols/rdp/keymaps/de_ch_qwertz.keymap b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap new file mode 100644 index 00000000..e8ae0d15 --- /dev/null +++ b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap @@ -0,0 +1,58 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "ch-de-qwertz" +freerdp "KBD_SWISS_GERMAN" + +# +# Basic keys +# + +map -altgr -shift 0x29 0x02..0x0C ~ "§1234567890'" +map -altgr -shift 0x10..0x1A ~ "qwertzuiopü" +map -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklöä$" +map -altgr -shift 0x56 0x2C..0x35 ~ "YXCVBNM;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02..0x04 ~ "¦@#" +map +altgr -shift 0x07..0x09 ~ "¬|¢" +map +altgr -shift 0x1A..0x1B ~ "[]" +map +altgr -shift 0x28 ~ "{" +map +altgr -shift 0x2B ~ "}" +map +altgr -shift 0x56 ~ "\" +map +altgr -shift 0x12 ~ "€" + +# +# Dead keys +# + +map +altgr -shift 0x0C ~ 0xFE51 # Dead acute +map -altgr -shift 0x0D ~ 0xFE52 # Dead circumflex +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map +altgr -shift 0x0D ~ 0xFE53 # Dead tilde +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut \ No newline at end of file From 01142e6dd8fcb266a584f7ecd2d5a7d0f00de328 Mon Sep 17 00:00:00 2001 From: Andrin Date: Tue, 4 Dec 2018 08:30:36 +0100 Subject: [PATCH 069/175] GUACAMOLE-354: Fixed keyboard name. --- src/protocols/rdp/keymaps/de_ch_qwertz.keymap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/keymaps/de_ch_qwertz.keymap b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap index e8ae0d15..59b9eba2 100644 --- a/src/protocols/rdp/keymaps/de_ch_qwertz.keymap +++ b/src/protocols/rdp/keymaps/de_ch_qwertz.keymap @@ -18,7 +18,7 @@ # parent "base" -name "ch-de-qwertz" +name "de-ch-qwertz" freerdp "KBD_SWISS_GERMAN" # From 47ad6f4b59dc861ae8f17e41885c7f2947ff2bad Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 6 Jan 2019 15:43:55 -0800 Subject: [PATCH 070/175] GUACAMOLE-662: Properly initialize nested socket index (fixes GUACAMOLE-510). --- src/libguac/socket-nest.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libguac/socket-nest.c b/src/libguac/socket-nest.c index 6acac7d9..0c7d4711 100644 --- a/src/libguac/socket-nest.c +++ b/src/libguac/socket-nest.c @@ -129,8 +129,9 @@ guac_socket* guac_socket_nest(guac_socket* parent, int index) { guac_socket* socket = guac_socket_alloc(); __guac_socket_nest_data* data = malloc(sizeof(__guac_socket_nest_data)); - /* Store file descriptor as socket data */ + /* Store nested socket details as socket data */ data->parent = parent; + data->index = index; socket->data = data; /* Set write and free handlers */ From cc4671d7a168cd7424282f9b4be7b59cc1ee36a2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 6 Jan 2019 17:06:45 -0800 Subject: [PATCH 071/175] GUACAMOLE-662: Correct handling of buffering within nested socket. The nested socket implementation seems to have never been properly updated since guac_socket was changed to rely on implementation-specific buffering. This meant that absolutely every write resulted in a nest instruction being sent to the parent socket. Data should instead be built up within the internal buffer, with each flush writing as much of the internal buffer as possible within a nest instruction, leaving any partial UTF-8 characters at the end of the buffer for later completion with future writes. --- src/libguac/socket-nest.c | 300 +++++++++++++++++++++++++++++++------- 1 file changed, 245 insertions(+), 55 deletions(-) diff --git a/src/libguac/socket-nest.c b/src/libguac/socket-nest.c index 0c7d4711..967c9d97 100644 --- a/src/libguac/socket-nest.c +++ b/src/libguac/socket-nest.c @@ -25,80 +25,237 @@ #include #include +#include #include -#define GUAC_SOCKET_NEST_BUFFER_SIZE 8192 +/** + * The maximum number of bytes to buffer before sending a "nest" instruction. + * As some of the 8 KB space available for each instruction will be taken up by + * the "nest" opcode and other parameters, and 1 KB will be more than enough + * space for that extra data, this space is reduced to an even 7 KB. + */ +#define GUAC_SOCKET_NEST_BUFFER_SIZE 7168 -typedef struct __guac_socket_nest_data { +/** + * Internal data associated with an open socket which writes via a series of + * "nest" instructions to some underlying, parent socket. + */ +typedef struct guac_socket_nest_data { + /** + * The underlying socket which should be used to write "nest" instructions. + */ guac_socket* parent; - char buffer[GUAC_SOCKET_NEST_BUFFER_SIZE]; + + /** + * The arbitrary index of the nested socket, assigned at time of + * allocation. + */ int index; -} __guac_socket_nest_data; + /** + * The number of bytes currently in the main write buffer. + */ + int written; -ssize_t __guac_socket_nest_write_handler(guac_socket* socket, - const void* buf, size_t count) { + /** + * The main write buffer. Bytes written go here before being flushed + * as nest instructions. Space is included for the null terminator + * required by guac_protocol_send_nest(). + */ + char buffer[GUAC_SOCKET_NEST_BUFFER_SIZE]; - __guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data; - unsigned char* source = (unsigned char*) buf; + /** + * Lock which is acquired when an instruction is being written, and + * released when the instruction is finished being written. + */ + pthread_mutex_t socket_lock; - /* Current location in destination buffer during copy */ - char* current = data->buffer; + /** + * Lock which protects access to the internal buffer of this socket, + * guaranteeing atomicity of writes and flushes. + */ + pthread_mutex_t buffer_lock; - /* Number of bytes remaining in source buffer */ - int remaining = count; +} guac_socket_nest_data; - /* If we can't actually store that many bytes, reduce number of bytes - * expected to be written */ - if (remaining > GUAC_SOCKET_NEST_BUFFER_SIZE) - remaining = GUAC_SOCKET_NEST_BUFFER_SIZE; +/** + * Flushes the contents of the output buffer of the given socket immediately, + * without first locking access to the output buffer. This function must ONLY + * be called if the buffer lock has already been acquired. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_nest_flush(guac_socket* socket) { - /* Current offset within destination buffer */ - int offset; + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; - /* Number of characters before start of next character */ - int skip = 0; + /* Flush remaining bytes in buffer */ + if (data->written > 0) { - /* Copy UTF-8 characters into buffer */ - for (offset = 0; offset < GUAC_SOCKET_NEST_BUFFER_SIZE; offset++) { + /* Determine length of buffer containing complete UTF-8 characters + * (buffer may end with a partial, multi-byte character) */ + int length = 0; + while (length < data->written) + length += guac_utf8_charsize(*(data->buffer + length)); - /* Get next byte */ - unsigned char c = *source; - remaining--; + /* Add null terminator, preserving overwritten character for later + * restoration (guac_protocol_send_nest() requires null-terminated + * strings) */ + char overwritten = data->buffer[length]; + data->buffer[length] = '\0'; - /* If skipping, then skip */ - if (skip > 0) skip--; + /* Write ALL bytes in buffer as nest instruction */ + int retval = guac_protocol_send_nest(data->parent, data->index, data->buffer); - /* Otherwise, determine next skip value, and increment length */ - else { + /* Restore original value overwritten by null terminator */ + data->buffer[length] = overwritten; - /* Determine skip value (size in bytes of rest of character) */ - skip = guac_utf8_charsize(c) - 1; + if (retval) + return 1; - /* If not enough bytes to complete character, break */ - if (skip > remaining) - break; - - } - - /* Store byte */ - *current = c; - - /* Advance to next character */ - source++; - current++; + /* Shift any remaining data to beginning of buffer */ + memcpy(data->buffer, data->buffer + length, data->written - length); + data->written -= length; } - /* Append null-terminator */ - *current = 0; + return 0; - /* Send nest instruction containing read UTF-8 segment */ - guac_protocol_send_nest(data->parent, data->index, data->buffer); +} - /* Return number of bytes actually written */ - return offset; +/** + * Flushes the internal buffer of the given guac_socket, writing all data + * to the underlying socket using "nest" instructions. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_nest_flush_handler(guac_socket* socket) { + + int retval; + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Flush contents of buffer */ + retval = guac_socket_nest_flush(socket); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Writes the contents of the buffer to the output buffer of the given socket, + * flushing the output buffer as necessary, without first locking access to the + * output buffer. This function must ONLY be called if the buffer lock has + * already been acquired. + * + * @param socket + * The guac_socket to write the given buffer to. + * + * @param buf + * The buffer to write to the given socket. + * + * @param count + * The number of bytes in the given buffer. + * + * @return + * The number of bytes written, or a negative value if an error occurs + * during write. + */ +static ssize_t guac_socket_nest_write_buffered(guac_socket* socket, + const void* buf, size_t count) { + + size_t original_count = count; + const char* current = buf; + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; + + /* Append to buffer, flush if necessary */ + while (count > 0) { + + int chunk_size; + + /* Calculate space remaining, including one extra byte for the null + * terminator added upon flush */ + int remaining = sizeof(data->buffer) - data->written - 1; + + /* If no space left in buffer, flush and retry */ + if (remaining == 0) { + + /* Abort if error occurs during flush */ + if (guac_socket_nest_flush(socket)) + return -1; + + /* Retry buffer append */ + continue; + + } + + /* Calculate size of chunk to be written to buffer */ + chunk_size = count; + if (chunk_size > remaining) + chunk_size = remaining; + + /* Update output buffer */ + memcpy(data->buffer + data->written, current, chunk_size); + data->written += chunk_size; + + /* Update provided buffer */ + current += chunk_size; + count -= chunk_size; + + } + + /* All bytes have been written, possibly some to the internal buffer */ + return original_count; + +} + +/** + * Appends the provided data to the internal buffer for future writing. The + * actual write attempt will occur only upon flush, or when the internal buffer + * is full. + * + * @param socket + * The guac_socket being write to. + * + * @param buf + * The arbitrary buffer containing the data to be written. + * + * @param count + * The number of bytes contained within the buffer. + * + * @return + * The number of bytes written, or -1 if an error occurs. + */ +static ssize_t guac_socket_nest_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + int retval; + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Write provided data to buffer */ + retval = guac_socket_nest_write_buffered(socket, buf, count); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; } @@ -113,30 +270,63 @@ ssize_t __guac_socket_nest_write_handler(guac_socket* socket, * Zero if the data was successfully freed, non-zero otherwise. This * implementation always succeeds, and will always return zero. */ -static int __guac_socket_nest_free_handler(guac_socket* socket) { +static int guac_socket_nest_free_handler(guac_socket* socket) { /* Free associated data */ - __guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data; + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; free(data); return 0; } +/** + * Acquires exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is required. + */ +static void guac_socket_nest_lock_handler(guac_socket* socket) { + + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; + + /* Acquire exclusive access to socket */ + pthread_mutex_lock(&(data->socket_lock)); + +} + +/** + * Relinquishes exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is no longer required. + */ +static void guac_socket_nest_unlock_handler(guac_socket* socket) { + + guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data; + + /* Relinquish exclusive access to socket */ + pthread_mutex_unlock(&(data->socket_lock)); + +} + guac_socket* guac_socket_nest(guac_socket* parent, int index) { /* Allocate socket and associated data */ guac_socket* socket = guac_socket_alloc(); - __guac_socket_nest_data* data = malloc(sizeof(__guac_socket_nest_data)); + guac_socket_nest_data* data = malloc(sizeof(guac_socket_nest_data)); /* Store nested socket details as socket data */ data->parent = parent; data->index = index; socket->data = data; - /* Set write and free handlers */ - socket->write_handler = __guac_socket_nest_write_handler; - socket->free_handler = __guac_socket_nest_free_handler; + /* Set relevant handlers */ + socket->write_handler = guac_socket_nest_write_handler; + socket->lock_handler = guac_socket_nest_lock_handler; + socket->unlock_handler = guac_socket_nest_unlock_handler; + socket->flush_handler = guac_socket_nest_flush_handler; + socket->free_handler = guac_socket_nest_free_handler; return socket; From d73b86b4b7c41fc7fa16423fc340d644c729b233 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 7 Jan 2019 09:37:08 -0800 Subject: [PATCH 072/175] GUACAMOLE-661: Mark "nest" instruction and socket as deprecated. --- src/libguac/guacamole/protocol.h | 7 +++++++ src/libguac/guacamole/socket.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index fd824af6..d1a0420a 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -214,6 +214,13 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y, * If an error occurs sending the instruction, a non-zero value is * returned, and guac_error is set appropriately. * + * @deprecated + * The "nest" instruction and the corresponding guac_socket + * implementation are no longer necessary, having been replaced by + * the streaming instructions ("blob", "ack", "end"). Code using nested + * sockets or the "nest" instruction should instead write to a normal + * socket directly. + * * @param socket The guac_socket connection to use. * @param index The integer index of the stram to send the protocol * data over. diff --git a/src/libguac/guacamole/socket.h b/src/libguac/guacamole/socket.h index e83a4ebc..2da697fe 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -180,6 +180,13 @@ guac_socket* guac_socket_open(int fd); * If an error occurs while allocating the guac_socket object, NULL is returned, * and guac_error is set appropriately. * + * @deprecated + * The "nest" instruction and the corresponding guac_socket + * implementation are no longer necessary, having been replaced by + * the streaming instructions ("blob", "ack", "end"). Code using nested + * sockets or the "nest" instruction should instead write to a normal + * socket directly. + * * @param parent The guac_socket this new guac_socket should write nest * instructions to. * @param index The stream index to use for the written nest instructions. From a06edb9deb203603c98cf8aea3fc85b0c51797ae Mon Sep 17 00:00:00 2001 From: netromnetrom <35839198+netromnetrom@users.noreply.github.com> Date: Tue, 22 Jan 2019 14:36:05 +0100 Subject: [PATCH 073/175] Update and rename dk_dk_qwerty.keymap to da_dk_qwerty.keymap --- .../rdp/keymaps/{dk_dk_qwerty.keymap => da_dk_qwerty.keymap} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/protocols/rdp/keymaps/{dk_dk_qwerty.keymap => da_dk_qwerty.keymap} (98%) diff --git a/src/protocols/rdp/keymaps/dk_dk_qwerty.keymap b/src/protocols/rdp/keymaps/da_dk_qwerty.keymap similarity index 98% rename from src/protocols/rdp/keymaps/dk_dk_qwerty.keymap rename to src/protocols/rdp/keymaps/da_dk_qwerty.keymap index 6295cabd..c4893d1b 100644 --- a/src/protocols/rdp/keymaps/dk_dk_qwerty.keymap +++ b/src/protocols/rdp/keymaps/da_dk_qwerty.keymap @@ -18,7 +18,7 @@ # parent "base" -name "dk-dk-qwerty" +name "da-dk-qwerty" freerdp "KBD_DANISH" # From 7b7c8a1b0226336122dbd7c37e6fc650e68762a4 Mon Sep 17 00:00:00 2001 From: netromnetrom <35839198+netromnetrom@users.noreply.github.com> Date: Tue, 22 Jan 2019 16:24:56 +0100 Subject: [PATCH 074/175] Update Makefile.am --- src/protocols/rdp/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index b58ddbb2..ec7beaf8 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -261,7 +261,7 @@ rdp_keymaps = \ $(srcdir)/keymaps/ja_jp_qwerty.keymap \ $(srcdir)/keymaps/pt_br_qwerty.keymap \ $(srcdir)/keymaps/sv_se_qwerty.keymap \ - $(srcdir)/keymaps/dk_dk_qwerty.keymap \ + $(srcdir)/keymaps/da_dk_qwerty.keymap \ $(srcdir)/keymaps/tr_tr_qwerty.keymap _generated_keymaps.c: $(rdp_keymaps) From 10e06c15c9d7099118f460542888543b8dc951f5 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 23 Jan 2019 17:15:13 -0500 Subject: [PATCH 075/175] GUACAMOLE-693: Update copyright year to 2019. --- NOTICE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index 97e61306..39de3ece 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Guacamole -Copyright 2018 The Apache Software Foundation +Copyright 2019 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). From f6953e131764284915e51bd495a510c7c592d59b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Oct 2018 09:30:20 -0700 Subject: [PATCH 076/175] GUACAMOLE-637: Use proper namespaced path for Guacamole headers within libguac source. --- src/libguac/Makefile.am | 2 +- src/libguac/audio.c | 11 +++++------ src/libguac/client.c | 20 ++++++++++---------- src/libguac/encode-jpeg.c | 6 +++--- src/libguac/encode-jpeg.h | 4 ++-- src/libguac/encode-png.c | 6 +++--- src/libguac/encode-png.h | 4 ++-- src/libguac/encode-webp.c | 6 +++--- src/libguac/encode-webp.h | 4 ++-- src/libguac/error.c | 2 +- src/libguac/id.c | 2 +- src/libguac/parser.c | 8 ++++---- src/libguac/pool.c | 2 +- src/libguac/protocol.c | 14 +++++++------- src/libguac/raw_encoder.c | 12 +++++------- src/libguac/raw_encoder.h | 2 +- src/libguac/socket-broadcast.c | 8 ++++---- src/libguac/socket-fd.c | 4 ++-- src/libguac/socket-nest.c | 6 +++--- src/libguac/socket-ssl.c | 6 +++--- src/libguac/socket-tee.c | 2 +- src/libguac/socket-wsa.c | 4 ++-- src/libguac/socket.c | 8 ++++---- src/libguac/timestamp.c | 2 +- src/libguac/unicode.c | 2 +- src/libguac/user-handlers.c | 12 ++++++------ src/libguac/user-handlers.h | 4 ++-- src/libguac/user-handshake.c | 12 ++++++------ src/libguac/user.c | 16 ++++++++-------- 29 files changed, 94 insertions(+), 97 deletions(-) diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 2757f706..876eb298 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -122,7 +122,7 @@ libguacinc_HEADERS += guacamole/socket-wsa.h endif libguac_la_CFLAGS = \ - -Werror -Wall -pedantic -I$(srcdir)/guacamole + -Werror -Wall -pedantic libguac_la_LDFLAGS = \ -version-info 16:0:0 \ diff --git a/src/libguac/audio.c b/src/libguac/audio.c index cf0187a2..cc577c71 100644 --- a/src/libguac/audio.c +++ b/src/libguac/audio.c @@ -19,14 +19,13 @@ #include "config.h" +#include "guacamole/audio.h" +#include "guacamole/client.h" +#include "guacamole/protocol.h" +#include "guacamole/stream.h" +#include "guacamole/user.h" #include "raw_encoder.h" -#include -#include -#include -#include -#include - #include #include diff --git a/src/libguac/client.c b/src/libguac/client.c index 4f3051d9..71cffda8 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -19,20 +19,20 @@ #include "config.h" -#include "client.h" #include "encode-jpeg.h" #include "encode-png.h" #include "encode-webp.h" -#include "error.h" +#include "guacamole/client.h" +#include "guacamole/error.h" +#include "guacamole/layer.h" +#include "guacamole/plugin.h" +#include "guacamole/pool.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" +#include "guacamole/timestamp.h" +#include "guacamole/user.h" #include "id.h" -#include "layer.h" -#include "pool.h" -#include "plugin.h" -#include "protocol.h" -#include "socket.h" -#include "stream.h" -#include "timestamp.h" -#include "user.h" #include #include diff --git a/src/libguac/encode-jpeg.c b/src/libguac/encode-jpeg.c index 5a869c72..1aeb23bb 100644 --- a/src/libguac/encode-jpeg.c +++ b/src/libguac/encode-jpeg.c @@ -20,10 +20,10 @@ #include "config.h" #include "encode-jpeg.h" -#include "error.h" +#include "guacamole/error.h" +#include "guacamole/protocol.h" +#include "guacamole/stream.h" #include "palette.h" -#include "protocol.h" -#include "stream.h" #include #include diff --git a/src/libguac/encode-jpeg.h b/src/libguac/encode-jpeg.h index d791ea69..05cf2cad 100644 --- a/src/libguac/encode-jpeg.h +++ b/src/libguac/encode-jpeg.h @@ -22,8 +22,8 @@ #include "config.h" -#include "socket.h" -#include "stream.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" #include diff --git a/src/libguac/encode-png.c b/src/libguac/encode-png.c index 78795577..7d4b6c7f 100644 --- a/src/libguac/encode-png.c +++ b/src/libguac/encode-png.c @@ -20,10 +20,10 @@ #include "config.h" #include "encode-png.h" -#include "error.h" +#include "guacamole/error.h" +#include "guacamole/protocol.h" +#include "guacamole/stream.h" #include "palette.h" -#include "protocol.h" -#include "stream.h" #include #include diff --git a/src/libguac/encode-png.h b/src/libguac/encode-png.h index 916493c4..222c50ef 100644 --- a/src/libguac/encode-png.h +++ b/src/libguac/encode-png.h @@ -22,8 +22,8 @@ #include "config.h" -#include "socket.h" -#include "stream.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" #include diff --git a/src/libguac/encode-webp.c b/src/libguac/encode-webp.c index 88534db5..5c2237d6 100644 --- a/src/libguac/encode-webp.c +++ b/src/libguac/encode-webp.c @@ -20,10 +20,10 @@ #include "config.h" #include "encode-webp.h" -#include "error.h" +#include "guacamole/error.h" +#include "guacamole/protocol.h" +#include "guacamole/stream.h" #include "palette.h" -#include "protocol.h" -#include "stream.h" #include #include diff --git a/src/libguac/encode-webp.h b/src/libguac/encode-webp.h index 8971765c..5347f6d9 100644 --- a/src/libguac/encode-webp.h +++ b/src/libguac/encode-webp.h @@ -22,8 +22,8 @@ #include "config.h" -#include "socket.h" -#include "stream.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" #include diff --git a/src/libguac/error.c b/src/libguac/error.c index 95942e81..5561b178 100644 --- a/src/libguac/error.c +++ b/src/libguac/error.c @@ -19,7 +19,7 @@ #include "config.h" -#include "error.h" +#include "guacamole/error.h" #include #include diff --git a/src/libguac/id.c b/src/libguac/id.c index 56ef23c2..27a714c0 100644 --- a/src/libguac/id.c +++ b/src/libguac/id.c @@ -19,8 +19,8 @@ #include "config.h" +#include "guacamole/error.h" #include "id.h" -#include "error.h" #ifdef HAVE_OSSP_UUID_H #include diff --git a/src/libguac/parser.c b/src/libguac/parser.c index 799b48ab..9645a2a3 100644 --- a/src/libguac/parser.c +++ b/src/libguac/parser.c @@ -19,10 +19,10 @@ #include "config.h" -#include "error.h" -#include "parser.h" -#include "socket.h" -#include "unicode.h" +#include "guacamole/error.h" +#include "guacamole/parser.h" +#include "guacamole/socket.h" +#include "guacamole/unicode.h" #include #include diff --git a/src/libguac/pool.c b/src/libguac/pool.c index 363c239d..9db43ada 100644 --- a/src/libguac/pool.c +++ b/src/libguac/pool.c @@ -19,7 +19,7 @@ #include "config.h" -#include "pool.h" +#include "guacamole/pool.h" #include diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index dba9c56e..ee266e87 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -19,14 +19,14 @@ #include "config.h" -#include "error.h" -#include "layer.h" -#include "object.h" +#include "guacamole/error.h" +#include "guacamole/layer.h" +#include "guacamole/object.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" +#include "guacamole/unicode.h" #include "palette.h" -#include "protocol.h" -#include "socket.h" -#include "stream.h" -#include "unicode.h" #include diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c index 1dd03cc4..9086a37b 100644 --- a/src/libguac/raw_encoder.c +++ b/src/libguac/raw_encoder.c @@ -19,15 +19,13 @@ #include "config.h" -#include "audio.h" +#include "guacamole/audio.h" +#include "guacamole/client.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/user.h" #include "raw_encoder.h" -#include -#include -#include -#include -#include - #include #include #include diff --git a/src/libguac/raw_encoder.h b/src/libguac/raw_encoder.h index b4050406..c3af151c 100644 --- a/src/libguac/raw_encoder.h +++ b/src/libguac/raw_encoder.h @@ -23,7 +23,7 @@ #include "config.h" -#include "audio.h" +#include "guacamole/audio.h" /** * The number of bytes to send in each audio blob. diff --git a/src/libguac/socket-broadcast.c b/src/libguac/socket-broadcast.c index a3f17fd0..f551e817 100644 --- a/src/libguac/socket-broadcast.c +++ b/src/libguac/socket-broadcast.c @@ -19,10 +19,10 @@ #include "config.h" -#include "client.h" -#include "error.h" -#include "socket.h" -#include "user.h" +#include "guacamole/client.h" +#include "guacamole/error.h" +#include "guacamole/socket.h" +#include "guacamole/user.h" #include #include diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c index 34898524..742cc35d 100644 --- a/src/libguac/socket-fd.c +++ b/src/libguac/socket-fd.c @@ -19,8 +19,8 @@ #include "config.h" -#include "error.h" -#include "socket.h" +#include "guacamole/error.h" +#include "guacamole/socket.h" #include "wait-fd.h" #include diff --git a/src/libguac/socket-nest.c b/src/libguac/socket-nest.c index 967c9d97..8bc9291e 100644 --- a/src/libguac/socket-nest.c +++ b/src/libguac/socket-nest.c @@ -19,9 +19,9 @@ #include "config.h" -#include "protocol.h" -#include "socket.h" -#include "unicode.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/unicode.h" #include #include diff --git a/src/libguac/socket-ssl.c b/src/libguac/socket-ssl.c index 1a631fe1..3daa128e 100644 --- a/src/libguac/socket-ssl.c +++ b/src/libguac/socket-ssl.c @@ -19,9 +19,9 @@ #include "config.h" -#include "error.h" -#include "socket-ssl.h" -#include "socket.h" +#include "guacamole/error.h" +#include "guacamole/socket-ssl.h" +#include "guacamole/socket.h" #include "wait-fd.h" #include diff --git a/src/libguac/socket-tee.c b/src/libguac/socket-tee.c index 3f0fd6bd..cee9108c 100644 --- a/src/libguac/socket-tee.c +++ b/src/libguac/socket-tee.c @@ -19,7 +19,7 @@ #include "config.h" -#include "socket.h" +#include "guacamole/socket.h" #include diff --git a/src/libguac/socket-wsa.c b/src/libguac/socket-wsa.c index 1b59176a..f5602e39 100644 --- a/src/libguac/socket-wsa.c +++ b/src/libguac/socket-wsa.c @@ -17,8 +17,8 @@ * under the License. */ -#include "error.h" -#include "socket.h" +#include "guacamole/error.h" +#include "guacamole/socket.h" #include #include diff --git a/src/libguac/socket.c b/src/libguac/socket.c index 6bc0036b..66442d0c 100644 --- a/src/libguac/socket.c +++ b/src/libguac/socket.c @@ -19,10 +19,10 @@ #include "config.h" -#include "error.h" -#include "protocol.h" -#include "socket.h" -#include "timestamp.h" +#include "guacamole/error.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/timestamp.h" #include #include diff --git a/src/libguac/timestamp.c b/src/libguac/timestamp.c index 0d2dc0a8..9020a6e1 100644 --- a/src/libguac/timestamp.c +++ b/src/libguac/timestamp.c @@ -19,7 +19,7 @@ #include "config.h" -#include "timestamp.h" +#include "guacamole/timestamp.h" #include diff --git a/src/libguac/unicode.c b/src/libguac/unicode.c index e7af943f..fdc0fffd 100644 --- a/src/libguac/unicode.c +++ b/src/libguac/unicode.c @@ -19,7 +19,7 @@ #include "config.h" -#include "unicode.h" +#include "guacamole/unicode.h" #include diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index b84dc721..6be20e2e 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -19,12 +19,12 @@ #include "config.h" -#include "client.h" -#include "object.h" -#include "protocol.h" -#include "stream.h" -#include "timestamp.h" -#include "user.h" +#include "guacamole/client.h" +#include "guacamole/object.h" +#include "guacamole/protocol.h" +#include "guacamole/stream.h" +#include "guacamole/timestamp.h" +#include "guacamole/user.h" #include "user-handlers.h" #include diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index eedeba15..5d7c6eae 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -31,8 +31,8 @@ #include "config.h" -#include "client.h" -#include "timestamp.h" +#include "guacamole/client.h" +#include "guacamole/timestamp.h" /** * Internal handler for Guacamole instructions. Instruction handlers will be diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index b6018888..13bea51f 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -19,12 +19,12 @@ #include "config.h" -#include "client.h" -#include "error.h" -#include "parser.h" -#include "protocol.h" -#include "socket.h" -#include "user.h" +#include "guacamole/client.h" +#include "guacamole/error.h" +#include "guacamole/parser.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/user.h" #include #include diff --git a/src/libguac/user.c b/src/libguac/user.c index 14ec75bf..16590060 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -19,18 +19,18 @@ #include "config.h" -#include "client.h" #include "encode-jpeg.h" #include "encode-png.h" #include "encode-webp.h" +#include "guacamole/client.h" +#include "guacamole/object.h" +#include "guacamole/pool.h" +#include "guacamole/protocol.h" +#include "guacamole/socket.h" +#include "guacamole/stream.h" +#include "guacamole/timestamp.h" +#include "guacamole/user.h" #include "id.h" -#include "object.h" -#include "pool.h" -#include "protocol.h" -#include "socket.h" -#include "stream.h" -#include "timestamp.h" -#include "user.h" #include "user-handlers.h" #include From d7909a77aa4fde9df8bb2086bc0141a03b949133 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Oct 2018 09:49:23 -0700 Subject: [PATCH 077/175] GUACAMOLE-637: Add strlcpy() implementation. Use libc strlcpy() if available. --- configure.ac | 5 +++ src/libguac/Makefile.am | 2 + src/libguac/guacamole/string.h | 68 ++++++++++++++++++++++++++++++++++ src/libguac/string.c | 52 ++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 src/libguac/guacamole/string.h create mode 100644 src/libguac/string.c diff --git a/configure.ac b/configure.ac index b63ed690..c3ea7366 100644 --- a/configure.ac +++ b/configure.ac @@ -120,6 +120,11 @@ AC_CHECK_DECL([poll], [Whether poll() is defined])],, [#include ]) +AC_CHECK_DECL([strlcpy], + [AC_DEFINE([HAVE_STRLCPY],, + [Whether strlcpy() is defined])],, + [#include ]) + # Typedefs AC_TYPE_SIZE_T AC_TYPE_SSIZE_T diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 876eb298..ffcfffc8 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -61,6 +61,7 @@ libguacinc_HEADERS = \ guacamole/socket-types.h \ guacamole/stream.h \ guacamole/stream-types.h \ + guacamole/string.h \ guacamole/timestamp.h \ guacamole/timestamp-types.h \ guacamole/unicode.h \ @@ -96,6 +97,7 @@ libguac_la_SOURCES = \ socket-fd.c \ socket-nest.c \ socket-tee.c \ + string.c \ timestamp.c \ unicode.c \ user.c \ diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h new file mode 100644 index 00000000..89561ab0 --- /dev/null +++ b/src/libguac/guacamole/string.h @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_STRING_H +#define GUAC_STRING_H + +/** + * Provides convenience functions for manipulating strings. + * + * @file string.h + */ + +#include +#include + +/** + * Copies a limited number of bytes from the given source string to the given + * destination buffer. The resulting buffer will always be null-terminated, + * even if doing so means that the intended string is truncated, unless the + * destination buffer has no space available at all. As this function always + * returns the length of the string it tried to create (the length of the + * source string), whether truncation has occurred can be detected by comparing + * the return value against the size of the destination buffer. If the value + * returned is greater than or equal to the size of the destination buffer, then + * the string has been truncated. + * + * The source and destination buffers MAY NOT overlap. + * + * @param dest + * The buffer which should receive the contents of the source string. This + * buffer will always be null terminated unless zero bytes are available + * within the buffer. + * + * @param src + * The source string to copy into the destination buffer. This string MUST + * be null terminated. + * + * @param n + * The number of bytes available within the destination buffer. If this + * value is zero, no bytes will be written to the destination buffer, and + * the destination buffer may not be null terminated. In all other cases, + * the destination buffer will always be null terminated, even if doing + * so means that the copied data from the source string will be truncated. + * + * @return + * The length of the copied string (the source string) in bytes, excluding + * the null terminator. + */ +size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n); + +#endif + diff --git a/src/libguac/string.c b/src/libguac/string.c new file mode 100644 index 00000000..fbcbcf59 --- /dev/null +++ b/src/libguac/string.c @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include +#include + +size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) { + +#ifdef HAVE_STRLCPY + return strlcpy(dest, src, n); +#else + /* Calculate actual length of desired string */ + size_t length = strlen(src); + + /* Copy nothing if there is no space */ + if (n <= 0) + return length; + + /* Calculate length of the string which will be copied */ + size_t copy_length = length; + if (copy_length >= n) + copy_length = n - 1; + + /* Copy only as much of string as possible, manually adding a null + * terminator */ + memcpy(dest, src, copy_length); + dest[copy_length] = '\0'; + + /* Return the overall length of the desired string */ + return length; +#endif + +} + From a78f254611ae9889928af07766b946b97e25ade5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Oct 2018 12:28:04 -0700 Subject: [PATCH 078/175] GUACAMOLE-637: Add strlcat() implementation. Use libc strlcat() if available. --- configure.ac | 5 +++++ src/libguac/guacamole/string.h | 39 ++++++++++++++++++++++++++++++++++ src/libguac/string.c | 11 ++++++++++ 3 files changed, 55 insertions(+) diff --git a/configure.ac b/configure.ac index c3ea7366..19396204 100644 --- a/configure.ac +++ b/configure.ac @@ -125,6 +125,11 @@ AC_CHECK_DECL([strlcpy], [Whether strlcpy() is defined])],, [#include ]) +AC_CHECK_DECL([strlcat], + [AC_DEFINE([HAVE_STRLCAT],, + [Whether strlcat() is defined])],, + [#include ]) + # Typedefs AC_TYPE_SIZE_T AC_TYPE_SSIZE_T diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index 89561ab0..b3a89c9c 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -64,5 +64,44 @@ */ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n); +/** + * Appends the given source string after the end of the given destination + * string, writing at most the given number of bytes. Both the source and + * destination strings MUST be null-terminated. The resulting buffer will + * always be null-terminated, even if doing so means that the intended string + * is truncated, unless the destination buffer has no space available at all. + * As this function always returns the length of the string it tried to create + * (the length of destination and source strings added together), whether + * truncation has occurred can be detected by comparing the return value + * against the size of the destination buffer. If the value returned is greater + * than or equal to the size of the destination buffer, then the string has + * been truncated. + * + * The source and destination buffers MAY NOT overlap. + * + * @param dest + * The buffer which should be appended with the contents of the source + * string. This buffer MUST already be null-terminated and will always be + * null-terminated unless zero bytes are available within the buffer. + * + * @param src + * The source string to append to the the destination buffer. This string + * MUST be null-terminated. + * + * @param n + * The number of bytes available within the destination buffer. If this + * value is not greater than zero, no bytes will be written to the + * destination buffer, and the destination buffer may not be + * null-terminated. In all other cases, the destination buffer will always + * be null-terminated, even if doing so means that the copied data from the + * source string will be truncated. + * + * @return + * The length of the string this function tried to create (the lengths of + * the source and destination strings added together) in bytes, excluding + * the null terminator. + */ +size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n); + #endif diff --git a/src/libguac/string.c b/src/libguac/string.c index fbcbcf59..879c5267 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -50,3 +50,14 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) { } +size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { + +#ifdef HAVE_STRLCPY + return strlcat(dest, src, n); +#else + int length = strnlen(dest, n); + return length + guac_strlcpy(dest + length, src, n - length); +#endif + +} + From 5bf6a1479c2481ac4e3b0b1dea847e37b2782ebd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Oct 2018 12:28:38 -0700 Subject: [PATCH 079/175] GUACAMOLE-637: Add convenience function for joining an array of strings using a given delimiter. --- src/libguac/guacamole/string.h | 48 ++++++++++++++++++++++++++++++++++ src/libguac/string.c | 24 +++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index b3a89c9c..479a8052 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -103,5 +103,53 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n); */ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n); +/** + * Concatenates each of the given strings, separated by the given delimiter, + * storing the result within a destination buffer. The number of bytes written + * will be no more than the given number of bytes, and the destination buffer + * is guaranteed to be null-terminated, even if doing so means that one or more + * of the intended strings are truncated or omitted from the end of the result, + * unless the destination buffer has no space available at all. As this + * function always returns the length of the string it tried to create (the + * length of all source strings and all delimiters added together), whether + * truncation has occurred can be detected by comparing the return value + * against the size of the destination buffer. If the value returned is greater + * than or equal to the size of the destination buffer, then the string has + * been truncated. + * + * The source strings, delimiter string, and destination buffer MAY NOT + * overlap. + * + * @param dest + * The buffer which should receive the result of joining the given strings. + * This buffer will always be null terminated unless zero bytes are + * available within the buffer. + * + * @param elements + * The elements to concatenate together, separated by the given delimiter. + * Each element MUST be null-terminated. + * + * @param nmemb + * The number of elements within the elements array. + * + * @param delim + * The delimiter to include between each pair of elements. + * + * @param n + * The number of bytes available within the destination buffer. If this + * value is not greater than zero, no bytes will be written to the + * destination buffer, and the destination buffer may not be null + * terminated. In all other cases, the destination buffer will always be + * null terminated, even if doing so means that the result will be + * truncated. + * + * @return + * The length of the string this function tried to create (the length of + * all source strings and all delimiters added together) in bytes, + * excluding the null terminator. + */ +size_t guac_strljoin(char* restrict dest, const char* restrict const* elements, + int nmemb, const char* restrict delim, size_t n); + #endif diff --git a/src/libguac/string.c b/src/libguac/string.c index 879c5267..c913b4a4 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -61,3 +61,27 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { } +size_t guac_strljoin(char* restrict dest, const char* restrict const* elements, + int nmemb, const char* restrict delim, size_t n) { + + int length = 0; + const char* restrict const* current = elements; + + /* If no elements are provided, nothing to do but ensure the destination + * buffer is null terminated */ + if (nmemb <= 0) + return guac_strlcpy(dest, "", n); + + /* Initialize destination buffer with first element */ + length += guac_strlcpy(dest, *current, n); + + /* Copy all remaining elements, separated by delimiter */ + for (current++; nmemb > 1; current++, nmemb--) { + length += guac_strlcat(dest + length, delim, n - length); + length += guac_strlcat(dest + length, *current, n - length); + } + + return length; + +} + From e5c1147cf6ebcc150d4a352d5245547dc2027b06 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 12 Nov 2018 15:19:33 -0800 Subject: [PATCH 080/175] GUACAMOLE-637: Replace usages of strncpy() with guac_strlcpy(). --- src/common-ssh/sftp.c | 26 +++++++++++----- src/common/clipboard.c | 4 +-- src/protocols/rdp/guac_svc/svc_service.c | 3 +- src/protocols/rdp/rdp_fs.c | 38 +++++++++++------------- src/protocols/rdp/rdp_settings.c | 9 +++--- src/protocols/rdp/rdp_stream.c | 5 ++-- src/protocols/rdp/rdp_svc.c | 14 ++++----- src/protocols/rdp/rdp_svc.h | 7 +++-- 8 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c index 8a53b264..51fa7cd5 100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -74,10 +75,15 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, path++; /* Copy path into component data for parsing */ - strncpy(path_component_data, path, sizeof(path_component_data) - 1); + int length = guac_strlcpy(path_component_data, path, + sizeof(path_component_data)); + + /* Fail if input path was truncated */ + if (length >= sizeof(path_component_data)) + return 1; /* Find path components within path */ - for (i = 0; i < sizeof(path_component_data) - 1; i++) { + for (i = 0; i < sizeof(path_component_data); i++) { /* If current character is a path separator, parse as component */ char c = path_component_data[i]; @@ -114,9 +120,6 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, return 1; } - /* Ensure last component is null-terminated */ - path_component_data[i] = 0; - /* Convert components back into path */ for (; path_depth > 0; path_depth--) { @@ -830,8 +833,17 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user, list_state->directory = dir; list_state->filesystem = filesystem; - strncpy(list_state->directory_name, name, - sizeof(list_state->directory_name) - 1); + + int length = guac_strlcpy(list_state->directory_name, name, + sizeof(list_state->directory_name)); + + /* Bail out if directory name is too long to store */ + if (length >= sizeof(list_state->directory_name)) { + guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory " + "\"%s\": Path too long", fullpath); + free(list_state); + return 0; + } /* Allocate stream for body */ guac_stream* stream = guac_user_alloc_stream(user); diff --git a/src/common/clipboard.c b/src/common/clipboard.c index eb2f5480..e02a7ca4 100644 --- a/src/common/clipboard.c +++ b/src/common/clipboard.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -131,8 +132,7 @@ void guac_common_clipboard_reset(guac_common_clipboard* clipboard, clipboard->length = 0; /* Assign given mimetype */ - strncpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype) - 1); - clipboard->mimetype[sizeof(clipboard->mimetype) - 1] = '\0'; + guac_strlcpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype)); pthread_mutex_unlock(&(clipboard->lock)); diff --git a/src/protocols/rdp/guac_svc/svc_service.c b/src/protocols/rdp/guac_svc/svc_service.c index 4a38cb38..c40c6c5f 100644 --- a/src/protocols/rdp/guac_svc/svc_service.c +++ b/src/protocols/rdp/guac_svc/svc_service.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef ENABLE_WINPR #include @@ -53,7 +54,7 @@ int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) { guac_rdp_svc* svc = (guac_rdp_svc*) entry_points_ex->pExtendedData; /* Init channel def */ - strncpy(svc_plugin->plugin.channel_def.name, svc->name, + guac_strlcpy(svc_plugin->plugin.channel_def.name, svc->name, GUAC_RDP_SVC_MAX_LENGTH); svc_plugin->plugin.channel_def.options = CHANNEL_OPTION_INITIALIZED diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index ab48cc2f..db58c77e 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -39,6 +39,7 @@ #include #include #include +#include #include guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, @@ -607,11 +608,11 @@ const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) { int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { int i; - int path_depth = 0; + int path_depth = 1; char path_component_data[GUAC_RDP_FS_MAX_PATH]; - const char* path_components[64]; + const char* path_components[64] = { "" }; - const char** current_path_component = &(path_components[0]); + const char** current_path_component = &(path_components[1]); const char* current_path_component_data = &(path_component_data[0]); /* If original path is not absolute, normalization fails */ @@ -622,10 +623,15 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { path++; /* Copy path into component data for parsing */ - strncpy(path_component_data, path, sizeof(path_component_data) - 1); + int length = guac_strlcpy(path_component_data, path, + sizeof(path_component_data)); + + /* Fail if input path was truncated */ + if (length >= sizeof(path_component_data)) + return 1; /* Find path components within path */ - for (i = 0; i < sizeof(path_component_data) - 1; i++) { + for (i = 0; i < sizeof(path_component_data); i++) { /* If current character is a path separator, parse as component */ char c = path_component_data[i]; @@ -666,9 +672,6 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { return 0; } - /* Ensure last component is null-terminated */ - path_component_data[i] = 0; - /* Convert components back into path */ for (; path_depth > 0; path_depth--) { @@ -691,26 +694,19 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path) { - int i; + int length; char combined_path[GUAC_RDP_FS_MAX_PATH]; - char* current = combined_path; /* Copy parent path */ - for (i=0; i #include +#include #include #ifdef ENABLE_WINPR @@ -1242,11 +1243,11 @@ void guac_rdp_push_settings(guac_client* client, /* Client name */ if (guac_settings->client_name != NULL) { #ifdef LEGACY_RDPSETTINGS - strncpy(rdp_settings->client_hostname, guac_settings->client_name, - RDP_CLIENT_HOSTNAME_SIZE - 1); + guac_strlcpy(rdp_settings->client_hostname, guac_settings->client_name, + RDP_CLIENT_HOSTNAME_SIZE); #else - strncpy(rdp_settings->ClientHostname, guac_settings->client_name, - RDP_CLIENT_HOSTNAME_SIZE - 1); + guac_strlcpy(rdp_settings->ClientHostname, guac_settings->client_name, + RDP_CLIENT_HOSTNAME_SIZE); #endif } diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c index 5dae3667..533ff07e 100644 --- a/src/protocols/rdp/rdp_stream.c +++ b/src/protocols/rdp/rdp_stream.c @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef HAVE_FREERDP_CLIENT_CLIPRDR_H #include @@ -504,8 +505,8 @@ int guac_rdp_download_get_handler(guac_user* user, guac_object* object, rdp_stream->type = GUAC_RDP_LS_STREAM; rdp_stream->ls_status.fs = fs; rdp_stream->ls_status.file_id = file_id; - strncpy(rdp_stream->ls_status.directory_name, name, - sizeof(rdp_stream->ls_status.directory_name) - 1); + guac_strlcpy(rdp_stream->ls_status.directory_name, name, + sizeof(rdp_stream->ls_status.directory_name)); /* Allocate stream for body */ guac_stream* stream = guac_user_alloc_stream(user); diff --git a/src/protocols/rdp/rdp_svc.c b/src/protocols/rdp/rdp_svc.c index 83537a86..0a6eb246 100644 --- a/src/protocols/rdp/rdp_svc.c +++ b/src/protocols/rdp/rdp_svc.c @@ -25,6 +25,7 @@ #include #include +#include #ifdef ENABLE_WINPR #include @@ -33,7 +34,6 @@ #endif #include -#include guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name) { @@ -44,16 +44,14 @@ guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name) { svc->plugin = NULL; svc->output_pipe = NULL; + /* Init name */ + int name_length = guac_strlcpy(svc->name, name, GUAC_RDP_SVC_MAX_LENGTH); + /* Warn about name length */ - if (strnlen(name, GUAC_RDP_SVC_MAX_LENGTH+1) > GUAC_RDP_SVC_MAX_LENGTH) + if (name_length >= GUAC_RDP_SVC_MAX_LENGTH) guac_client_log(client, GUAC_LOG_INFO, "Static channel name \"%s\" exceeds maximum of %i characters " - "and will be truncated", - name, GUAC_RDP_SVC_MAX_LENGTH); - - /* Init name */ - strncpy(svc->name, name, GUAC_RDP_SVC_MAX_LENGTH); - svc->name[GUAC_RDP_SVC_MAX_LENGTH] = '\0'; + "and will be truncated", name, GUAC_RDP_SVC_MAX_LENGTH - 1); return svc; } diff --git a/src/protocols/rdp/rdp_svc.h b/src/protocols/rdp/rdp_svc.h index 322c4d9a..ebb3a13f 100644 --- a/src/protocols/rdp/rdp_svc.h +++ b/src/protocols/rdp/rdp_svc.h @@ -27,9 +27,10 @@ #include /** - * The maximum number of characters to allow for each channel name. + * The maximum number of bytes to allow within each channel name, including + * null terminator. */ -#define GUAC_RDP_SVC_MAX_LENGTH 7 +#define GUAC_RDP_SVC_MAX_LENGTH 8 /** * Structure describing a static virtual channel, and the corresponding @@ -50,7 +51,7 @@ typedef struct guac_rdp_svc { /** * The name of the RDP channel in use, and the name to use for each pipe. */ - char name[GUAC_RDP_SVC_MAX_LENGTH+1]; + char name[GUAC_RDP_SVC_MAX_LENGTH]; /** * The output pipe, opened when the RDP server receives a connection to From 3549da0dd10cef55833a6baaccdf06ca3521427d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 12 Nov 2018 15:38:02 -0800 Subject: [PATCH 081/175] GUACAMOLE-637: Replace usages of strncat() with guac_strlcat(). --- src/libguac/client.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libguac/client.c b/src/libguac/client.c index 71cffda8..80eb4ea7 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -30,6 +30,7 @@ #include "guacamole/protocol.h" #include "guacamole/socket.h" #include "guacamole/stream.h" +#include "guacamole/string.h" #include "guacamole/timestamp.h" #include "guacamole/user.h" #include "id.h" @@ -441,8 +442,13 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) { } alias; /* Add protocol and .so suffix to protocol_lib */ - strncat(protocol_lib, protocol, GUAC_PROTOCOL_NAME_LIMIT-1); - strcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX); + guac_strlcat(protocol_lib, protocol, sizeof(protocol_lib)); + if (guac_strlcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX, + sizeof(protocol_lib)) >= sizeof(protocol_lib)) { + guac_error = GUAC_STATUS_NO_MEMORY; + guac_error_message = "Protocol name is too long"; + return -1; + } /* Load client plugin */ client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY); From fdd3292f09f37eb12fc71f9d59470a863f9aa590 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 12 Nov 2018 16:10:24 -0800 Subject: [PATCH 082/175] GUACAMOLE-637: Simplify path translation logic. Update to use guac_strl*(). Fix return values. --- src/common-ssh/sftp.c | 138 +++++++++++++------------------------ src/protocols/rdp/rdp_fs.c | 28 +++----- src/protocols/rdp/rdp_fs.h | 5 ++ 3 files changed, 64 insertions(+), 107 deletions(-) diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c index 51fa7cd5..0a24c049 100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@ -64,12 +64,11 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, char path_component_data[GUAC_COMMON_SSH_SFTP_MAX_PATH]; const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH]; - const char** current_path_component = &(path_components[0]); - const char* current_path_component_data = &(path_component_data[0]); + const char* current_path_component_data = &(path_component_data[0]); /* If original path is not absolute, normalization fails */ if (path[0] != '\\' && path[0] != '/') - return 1; + return 0; /* Skip past leading slash */ path++; @@ -80,7 +79,7 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, /* Fail if input path was truncated */ if (length >= sizeof(path_component_data)) - return 1; + return 0; /* Find path components within path */ for (i = 0; i < sizeof(path_component_data); i++) { @@ -100,9 +99,16 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, /* Otherwise, if component not current directory, add to list */ else if (strcmp(current_path_component_data, ".") != 0 - && strcmp(current_path_component_data, "") != 0) + && strcmp(current_path_component_data, "") != 0) { + + /* Fail normalization if path is too deep */ + if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH) + return 0; + path_components[path_depth++] = current_path_component_data; + } + /* If end of string, stop */ if (c == '\0') break; @@ -121,21 +127,9 @@ static int guac_common_ssh_sftp_normalize_path(char* fullpath, } /* Convert components back into path */ - for (; path_depth > 0; path_depth--) { + guac_strljoin(fullpath, path_components, path_depth, + "/", GUAC_COMMON_SSH_SFTP_MAX_PATH); - const char* filename = *(current_path_component++); - - /* Add separator */ - *(fullpath++) = '/'; - - /* Copy string */ - while (*filename != 0) - *(fullpath++) = *(filename++); - - } - - /* Terminate absolute path */ - *(fullpath++) = 0; return 1; } @@ -232,7 +226,7 @@ static guac_protocol_status guac_sftp_get_status( static int guac_ssh_append_filename(char* fullpath, const char* path, const char* filename) { - int i; + int length; /* Disallow "." as a filename */ if (strcmp(filename, ".") == 0) @@ -242,49 +236,29 @@ static int guac_ssh_append_filename(char* fullpath, const char* path, if (strcmp(filename, "..") == 0) return 0; - /* Copy path, append trailing slash */ - for (i=0; i 0 && path[i-1] != '/') - fullpath[i++] = '/'; - break; - } - - /* Copy character if not end of string */ - fullpath[i] = c; - - } - - /* Append filename */ - for (; i 0 && fullpath[length - 1] != '/') + length += guac_strlcpy(fullpath + length, "/", + GUAC_COMMON_SSH_SFTP_MAX_PATH - length); + + /* Append filename */ + length += guac_strlcpy(fullpath + length, filename, + GUAC_COMMON_SSH_SFTP_MAX_PATH - length); + + /* Verify path length is within maximum */ + if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) + return 0; /* Append was successful */ return 1; @@ -313,46 +287,30 @@ static int guac_ssh_append_filename(char* fullpath, const char* path, static int guac_ssh_append_path(char* fullpath, const char* path_a, const char* path_b) { - int i; + int length; - /* Copy path, appending a trailing slash */ - for (i = 0; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) { + /* Copy first half of path */ + length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH); + if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) + return 0; - char c = path_a[i]; - if (c == '\0') { - if (i > 0 && path_a[i-1] != '/') - fullpath[i++] = '/'; - break; - } - - /* Copy character if not end of string */ - fullpath[i] = c; - - } + /* Ensure path ends with trailing slash */ + if (length == 0 || fullpath[length - 1] != '/') + length += guac_strlcpy(fullpath + length, "/", + GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Skip past leading slashes in second path */ while (*path_b == '/') path_b++; - /* Append path */ - for (; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) { - - char c = *(path_b++); - if (c == '\0') - break; - - /* Append each character within path */ - fullpath[i] = c; - - } + /* Append final half of path */ + length += guac_strlcpy(fullpath + length, path_b, + GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Verify path length is within maximum */ - if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH) + if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) return 0; - /* Terminate path string */ - fullpath[i] = '\0'; - /* Append was successful */ return 1; diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index db58c77e..3319e1db 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -610,9 +610,8 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { int i; int path_depth = 1; char path_component_data[GUAC_RDP_FS_MAX_PATH]; - const char* path_components[64] = { "" }; + const char* path_components[GUAC_RDP_MAX_PATH_DEPTH] = { "" }; - const char** current_path_component = &(path_components[1]); const char* current_path_component_data = &(path_component_data[0]); /* If original path is not absolute, normalization fails */ @@ -648,9 +647,16 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { /* Otherwise, if component not current directory, add to list */ else if (strcmp(current_path_component_data, ".") != 0 - && strcmp(current_path_component_data, "") != 0) + && strcmp(current_path_component_data, "") != 0) { + + /* Fail normalization if path is too deep */ + if (path_depth >= GUAC_RDP_MAX_PATH_DEPTH) + return 1; + path_components[path_depth++] = current_path_component_data; + } + /* If end of string, stop */ if (c == 0) break; @@ -673,21 +679,9 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { } /* Convert components back into path */ - for (; path_depth > 0; path_depth--) { + guac_strljoin(abs_path, path_components, path_depth, + "\\", GUAC_RDP_FS_MAX_PATH); - const char* filename = *(current_path_component++); - - /* Add separator */ - *(abs_path++) = '\\'; - - /* Copy string */ - while (*filename != 0) - *(abs_path++) = *(filename++); - - } - - /* Terminate absolute path */ - *(abs_path++) = 0; return 0; } diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/rdp_fs.h index 9168efb1..7ceb5ff9 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/rdp_fs.h @@ -50,6 +50,11 @@ */ #define GUAC_RDP_FS_MAX_PATH 4096 +/** + * The maximum number of directories a path may contain. + */ +#define GUAC_RDP_MAX_PATH_DEPTH 64 + /** * Error code returned when no more file IDs can be allocated. */ From dec364290590fa2e5693ead50db6f6f50805e761 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 19 Nov 2018 12:37:24 -0800 Subject: [PATCH 083/175] GUACAMOLE-637: Add unit tests for guac_strlcpy(). --- src/libguac/tests/Makefile.am | 1 + src/libguac/tests/string/strlcpy.c | 100 +++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/libguac/tests/string/strlcpy.c diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index 414a2f45..5d0b7024 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -42,6 +42,7 @@ test_libguac_SOURCES = \ protocol/base64_decode.c \ socket/fd_send_instruction.c \ socket/nested_send_instruction.c \ + string/strlcpy.c \ unicode/charsize.c \ unicode/read.c \ unicode/strlen.c \ diff --git a/src/libguac/tests/string/strlcpy.c b/src/libguac/tests/string/strlcpy.c new file mode 100644 index 00000000..d543ee63 --- /dev/null +++ b/src/libguac/tests/string/strlcpy.c @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include +#include + +/** + * Verify guac_strlcpy() behavior when string fits buffer without truncation. + */ +void test_string__strlcpy() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", sizeof(buffer)), 9); + CU_ASSERT_STRING_EQUAL(buffer, "Guacamole"); + CU_ASSERT_EQUAL(buffer[10], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", sizeof(buffer)), 14); + CU_ASSERT_STRING_EQUAL(buffer, "This is a test"); + CU_ASSERT_EQUAL(buffer[15], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "X", sizeof(buffer)), 1); + CU_ASSERT_STRING_EQUAL(buffer, "X"); + CU_ASSERT_EQUAL(buffer[2], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "", sizeof(buffer)), 0); + CU_ASSERT_STRING_EQUAL(buffer, ""); + CU_ASSERT_EQUAL(buffer[1], '\xFF'); + +} + +/** + * Verify guac_strlcpy() behavior when string must be truncated to fit buffer. + */ +void test_string__strlcpy_truncate() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", 6), 9); + CU_ASSERT_STRING_EQUAL(buffer, "Guaca"); + CU_ASSERT_EQUAL(buffer[6], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", 10), 14); + CU_ASSERT_STRING_EQUAL(buffer, "This is a"); + CU_ASSERT_EQUAL(buffer[10], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is ANOTHER test", 2), 20); + CU_ASSERT_STRING_EQUAL(buffer, "T"); + CU_ASSERT_EQUAL(buffer[2], '\xFF'); + +} + +/** + * Verify guac_strlcpy() behavior with zero buffer sizes. + */ +void test_string__strlcpy_nospace() { + + /* 0-byte buffer plus 1 guard byte (to test overrun) */ + char buffer[1] = { '\xFF' }; + + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", 0), 9); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", 0), 14); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "X", 0), 1); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcpy(buffer, "", 0), 0); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + +} + From 068f33aaef01e0693bd773ead220f12e18461f16 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 19 Nov 2018 23:16:22 -0800 Subject: [PATCH 084/175] GUACAMOLE-637: Add unit tests for guac_strlcat(). --- src/libguac/tests/Makefile.am | 1 + src/libguac/tests/string/strlcat.c | 153 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/libguac/tests/string/strlcat.c diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index 5d0b7024..49c2415f 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -42,6 +42,7 @@ test_libguac_SOURCES = \ protocol/base64_decode.c \ socket/fd_send_instruction.c \ socket/nested_send_instruction.c \ + string/strlcat.c \ string/strlcpy.c \ unicode/charsize.c \ unicode/read.c \ diff --git a/src/libguac/tests/string/strlcat.c b/src/libguac/tests/string/strlcat.c new file mode 100644 index 00000000..739f1ee1 --- /dev/null +++ b/src/libguac/tests/string/strlcat.c @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include +#include + +/** + * Verify guac_strlcat() behavior when string fits buffer without truncation. + * The return value of each call should be the length of the resulting string. + * Each resulting string should contain the full result of the contatenation, + * including null terminator. + */ +void test_string__strlcat() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, "Apache "); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", sizeof(buffer)), 16); + CU_ASSERT_STRING_EQUAL(buffer, "Apache Guacamole"); + CU_ASSERT_EQUAL(buffer[17], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, ""); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", sizeof(buffer)), 14); + CU_ASSERT_STRING_EQUAL(buffer, "This is a test"); + CU_ASSERT_EQUAL(buffer[15], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, "AB"); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", sizeof(buffer)), 3); + CU_ASSERT_STRING_EQUAL(buffer, "ABX"); + CU_ASSERT_EQUAL(buffer[4], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, "X"); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "", sizeof(buffer)), 1); + CU_ASSERT_STRING_EQUAL(buffer, "X"); + CU_ASSERT_EQUAL(buffer[2], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, ""); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "", sizeof(buffer)), 0); + CU_ASSERT_STRING_EQUAL(buffer, ""); + CU_ASSERT_EQUAL(buffer[1], '\xFF'); + +} + +/** + * Verify guac_strlcat() behavior when string must be truncated to fit buffer. + * The return value of each call should be the length that would result from + * concatenating the strings given an infinite buffer, however only as many + * characters as can fit should be appended to the string within the buffer, + * and the buffer should be null-terminated. + */ +void test_string__strlcat_truncate() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, "Apache "); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 9), 16); + CU_ASSERT_STRING_EQUAL(buffer, "Apache G"); + CU_ASSERT_EQUAL(buffer[9], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, ""); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 10), 14); + CU_ASSERT_STRING_EQUAL(buffer, "This is a"); + CU_ASSERT_EQUAL(buffer[10], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + strcpy(buffer, "This "); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "is ANOTHER test", 6), 20); + CU_ASSERT_STRING_EQUAL(buffer, "This "); + CU_ASSERT_EQUAL(buffer[6], '\xFF'); + +} + +/** + * Verify guac_strlcat() behavior with zero buffer sizes. The return value of + * each call should be the size of the input string, while the buffer remains + * untouched. + */ +void test_string__strlcat_nospace() { + + /* 0-byte buffer plus 1 guard byte (to test overrun) */ + char buffer[1] = { '\xFF' }; + + CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 0), 9); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 0), 14); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", 0), 1); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strlcat(buffer, "", 0), 0); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + +} + +/** + * Verify guac_strlcat() behavior with unterminated buffers. With respect to + * the return value, the length of the string in the buffer should be + * considered equal to the size of the buffer, however the resulting buffer + * should not be null-terminated. + */ +void test_string__strlcat_nonull() { + + char expected[1024]; + memset(expected, 0xFF, sizeof(expected)); + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 256), 265); + CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected)); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 37), 51); + CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected)); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", 12), 13); + CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected)); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strlcat(buffer, "", 100), 100); + CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected)); + +} + From 258946cd88edd5bbb226ac4af3cb6c509a2a5085 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 6 Jan 2019 15:03:54 -0800 Subject: [PATCH 085/175] GUACAMOLE-637: Correctly handle string lengths as size_t (unsigned). --- src/libguac/string.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libguac/string.c b/src/libguac/string.c index c913b4a4..f05c4c06 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -22,6 +22,26 @@ #include #include +/** + * Returns the space remaining in a buffer assuming that the given number of + * bytes have already been written. If the number of bytes exceeds the size + * of the buffer, zero is returned. + * + * @param n + * The size of the buffer in bytes. + * + * @param length + * The number of bytes which have been written to the buffer so far. If + * the routine writing the bytes will automatically truncate its writes, + * this value may exceed the size of the buffer. + * + * @return + * The number of bytes remaining in the buffer. This value will always + * be non-negative. If the number of bytes written already exceeds the + * size of the buffer, zero will be returned. + */ +#define REMAINING(n, length) (((n) < (length)) ? 0 : ((n) - (length))) + size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) { #ifdef HAVE_STRLCPY @@ -31,7 +51,7 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) { size_t length = strlen(src); /* Copy nothing if there is no space */ - if (n <= 0) + if (n == 0) return length; /* Calculate length of the string which will be copied */ @@ -55,8 +75,8 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { #ifdef HAVE_STRLCPY return strlcat(dest, src, n); #else - int length = strnlen(dest, n); - return length + guac_strlcpy(dest + length, src, n - length); + size_t length = strnlen(dest, n); + return length + guac_strlcpy(dest + length, src, REMAINING(n, length)); #endif } @@ -64,7 +84,7 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { size_t guac_strljoin(char* restrict dest, const char* restrict const* elements, int nmemb, const char* restrict delim, size_t n) { - int length = 0; + size_t length = 0; const char* restrict const* current = elements; /* If no elements are provided, nothing to do but ensure the destination @@ -77,8 +97,8 @@ size_t guac_strljoin(char* restrict dest, const char* restrict const* elements, /* Copy all remaining elements, separated by delimiter */ for (current++; nmemb > 1; current++, nmemb--) { - length += guac_strlcat(dest + length, delim, n - length); - length += guac_strlcat(dest + length, *current, n - length); + length += guac_strlcat(dest + length, delim, REMAINING(n, length)); + length += guac_strlcat(dest + length, *current, REMAINING(n, length)); } return length; From e6c5da315e79581d6aacd99989470783737e4b29 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 6 Jan 2019 15:26:45 -0800 Subject: [PATCH 086/175] GUACAMOLE-637: Add unit tests for guac_strljoin(). --- src/libguac/tests/Makefile.am | 1 + src/libguac/tests/string/strljoin.c | 148 ++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/libguac/tests/string/strljoin.c diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index 49c2415f..01f4c4cf 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -44,6 +44,7 @@ test_libguac_SOURCES = \ socket/nested_send_instruction.c \ string/strlcat.c \ string/strlcpy.c \ + string/strljoin.c \ unicode/charsize.c \ unicode/read.c \ unicode/strlen.c \ diff --git a/src/libguac/tests/string/strljoin.c b/src/libguac/tests/string/strljoin.c new file mode 100644 index 00000000..eb7f44e7 --- /dev/null +++ b/src/libguac/tests/string/strljoin.c @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include +#include + +/** + * Array of test elements containing the strings "Apache" and "Guacamole". + */ +const char* const apache_guacamole[] = { "Apache", "Guacamole" }; + +/** + * Array of test elements containing the strings "This", "is", "a", and "test". + */ +const char* const this_is_a_test[] = { "This", "is", "a", "test" }; + +/** + * Array of four test elements containing the strings "A" and "B", each + * preceded by an empty string (""). + */ +const char* const empty_a_empty_b[] = { "", "A", "", "B" }; + +/** + * Array of test elements containing ten empty strings. + */ +const char* const empty_x10[] = { "", "", "", "", "", "", "", "", "", "" }; + +/** + * Verify guac_strljoin() behavior when string fits buffer without truncation. + * The return value of each call should be the length of the resulting string. + * Each resulting string should contain the full result of the join operation, + * including null terminator. + */ +void test_string__strljoin() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", sizeof(buffer)), 16); + CU_ASSERT_STRING_EQUAL(buffer, "Apache Guacamole"); + CU_ASSERT_EQUAL(buffer[17], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", sizeof(buffer)), 11); + CU_ASSERT_STRING_EQUAL(buffer, "Thisisatest"); + CU_ASSERT_EQUAL(buffer[12], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", sizeof(buffer)), 20); + CU_ASSERT_STRING_EQUAL(buffer, "This-/-is-/-a-/-test"); + CU_ASSERT_EQUAL(buffer[21], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", sizeof(buffer)), 5); + CU_ASSERT_STRING_EQUAL(buffer, "/A//B"); + CU_ASSERT_EQUAL(buffer[6], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", sizeof(buffer)), 9); + CU_ASSERT_STRING_EQUAL(buffer, "/////////"); + CU_ASSERT_EQUAL(buffer[10], '\xFF'); + +} + +/** + * Verify guac_strljoin() behavior when string must be truncated to fit buffer. + * The return value of each call should be the length that would result from + * joining the strings given an infinite buffer, however only as many + * characters as can fit should be appended to the string within the buffer, + * and the buffer should be null-terminated. + */ +void test_string__strljoin_truncate() { + + char buffer[1024]; + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", 9), 16); + CU_ASSERT_STRING_EQUAL(buffer, "Apache G"); + CU_ASSERT_EQUAL(buffer[9], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", 8), 11); + CU_ASSERT_STRING_EQUAL(buffer, "Thisisa"); + CU_ASSERT_EQUAL(buffer[8], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", 12), 20); + CU_ASSERT_STRING_EQUAL(buffer, "This-/-is-/"); + CU_ASSERT_EQUAL(buffer[12], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", 2), 5); + CU_ASSERT_STRING_EQUAL(buffer, "/"); + CU_ASSERT_EQUAL(buffer[2], '\xFF'); + + memset(buffer, 0xFF, sizeof(buffer)); + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", 7), 9); + CU_ASSERT_STRING_EQUAL(buffer, "//////"); + CU_ASSERT_EQUAL(buffer[7], '\xFF'); + +} + +/** + * Verify guac_strljoin() behavior with zero buffer sizes. The return value of + * each call should be the size of the input string, while the buffer remains + * untouched. + */ +void test_string__strljoin_nospace() { + + /* 0-byte buffer plus 1 guard byte (to test overrun) */ + char buffer[1] = { '\xFF' }; + + CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", 0), 16); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", 0), 11); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", 0), 20); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", 0), 5); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + + CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", 0), 9); + CU_ASSERT_EQUAL(buffer[0], '\xFF'); + +} + From 7da837b42aaf9fc67e16021d9659e133773adf41 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 18:38:20 -0800 Subject: [PATCH 087/175] GUACAMOLE-637: The __BSD_VISIBLE macro is required for strlcpy() and strlcat() to be available in libc's string.h. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 19396204..7ea60298 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,7 @@ AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/socket.h time.h sys/time.h syslo # Source characteristics AC_DEFINE([_XOPEN_SOURCE], [700], [Uses X/Open and POSIX APIs]) +AC_DEFINE([__BSD_VISIBLE], [1], [Uses BSD-specific APIs (if available)]) # Check for whether math library is required AC_CHECK_LIB([m], [cos], From 789e3883d698d693cd1cf4165f80e8c59448d753 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 18:39:26 -0800 Subject: [PATCH 088/175] GUACAMOLE-637: Not all systems place Perl in /usr/bin. The line `#!/usr/bin/env perl` should be used for portability. --- src/protocols/rdp/keymaps/generate.pl | 2 +- util/generate-test-runner.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/keymaps/generate.pl b/src/protocols/rdp/keymaps/generate.pl index d3764d6e..263b616a 100755 --- a/src/protocols/rdp/keymaps/generate.pl +++ b/src/protocols/rdp/keymaps/generate.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file diff --git a/util/generate-test-runner.pl b/util/generate-test-runner.pl index 7534f805..ea99f7e7 100755 --- a/util/generate-test-runner.pl +++ b/util/generate-test-runner.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file From b7761e9a2e56d2b3e8261d99420c1a50da31081e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 18:41:41 -0800 Subject: [PATCH 089/175] GUACAMOLE-637: The `$^` variable is non-portable and specific to GNU Make. As otherwise POSIX-compliant platforms may not provide this variable, we shouldn't use it here. --- README-unit-testing.md | 2 +- src/common/tests/Makefile.am | 2 +- src/libguac/tests/Makefile.am | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README-unit-testing.md b/README-unit-testing.md index 5bed8721..8b4e7768 100644 --- a/README-unit-testing.md +++ b/README-unit-testing.md @@ -60,7 +60,7 @@ modified to contain a sections like the following: CLEANFILES = _generated_runner.c _generated_runner.c: $(test_myproj_SOURCES) - $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + $(AM_V_GEN) $(GEN_RUNNER) $(test_myproj_SOURCES) > $@ nodist_test_libguac_SOURCES = \ _generated_runner.c diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am index 8169d7d7..a9c1b559 100644 --- a/src/common/tests/Makefile.am +++ b/src/common/tests/Makefile.am @@ -60,7 +60,7 @@ GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl CLEANFILES = _generated_runner.c _generated_runner.c: $(test_common_SOURCES) - $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + $(AM_V_GEN) $(GEN_RUNNER) $(test_common_SOURCES) > $@ nodist_test_common_SOURCES = \ _generated_runner.c diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index 01f4c4cf..4ab3269f 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -67,7 +67,7 @@ GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl CLEANFILES = _generated_runner.c _generated_runner.c: $(test_libguac_SOURCES) - $(AM_V_GEN) $(GEN_RUNNER) $^ > $@ + $(AM_V_GEN) $(GEN_RUNNER) $(test_libguac_SOURCES) > $@ nodist_test_libguac_SOURCES = \ _generated_runner.c From 9fb713d804c30fe9c7f404d41911f6c95a3649d0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 18:51:53 -0800 Subject: [PATCH 090/175] GUACAMOLE-637: Correct grammar of documentation for guac_strl*() unit tests. --- src/libguac/tests/string/strlcat.c | 18 +++++++++--------- src/libguac/tests/string/strlcpy.c | 6 ++++-- src/libguac/tests/string/strljoin.c | 14 +++++++------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/libguac/tests/string/strlcat.c b/src/libguac/tests/string/strlcat.c index 739f1ee1..fb0c62aa 100644 --- a/src/libguac/tests/string/strlcat.c +++ b/src/libguac/tests/string/strlcat.c @@ -24,10 +24,10 @@ #include /** - * Verify guac_strlcat() behavior when string fits buffer without truncation. - * The return value of each call should be the length of the resulting string. - * Each resulting string should contain the full result of the contatenation, - * including null terminator. + * Verify guac_strlcat() behavior when the string fits the buffer without + * truncation. The return value of each call should be the length of the + * resulting string. Each resulting string should contain the full result of + * the contatenation, including null terminator. */ void test_string__strlcat() { @@ -66,11 +66,11 @@ void test_string__strlcat() { } /** - * Verify guac_strlcat() behavior when string must be truncated to fit buffer. - * The return value of each call should be the length that would result from - * concatenating the strings given an infinite buffer, however only as many - * characters as can fit should be appended to the string within the buffer, - * and the buffer should be null-terminated. + * Verify guac_strlcat() behavior when the string must be truncated to fit the + * buffer. The return value of each call should be the length that would result + * from concatenating the strings given an infinite buffer, however only as + * many characters as can fit should be appended to the string within the + * buffer, and the buffer should be null-terminated. */ void test_string__strlcat_truncate() { diff --git a/src/libguac/tests/string/strlcpy.c b/src/libguac/tests/string/strlcpy.c index d543ee63..1e8e01e3 100644 --- a/src/libguac/tests/string/strlcpy.c +++ b/src/libguac/tests/string/strlcpy.c @@ -24,7 +24,8 @@ #include /** - * Verify guac_strlcpy() behavior when string fits buffer without truncation. + * Verify guac_strlcpy() behavior when the string fits the buffer without + * truncation. */ void test_string__strlcpy() { @@ -53,7 +54,8 @@ void test_string__strlcpy() { } /** - * Verify guac_strlcpy() behavior when string must be truncated to fit buffer. + * Verify guac_strlcpy() behavior when the string must be truncated to fit the + * buffer. */ void test_string__strlcpy_truncate() { diff --git a/src/libguac/tests/string/strljoin.c b/src/libguac/tests/string/strljoin.c index eb7f44e7..39932885 100644 --- a/src/libguac/tests/string/strljoin.c +++ b/src/libguac/tests/string/strljoin.c @@ -45,10 +45,10 @@ const char* const empty_a_empty_b[] = { "", "A", "", "B" }; const char* const empty_x10[] = { "", "", "", "", "", "", "", "", "", "" }; /** - * Verify guac_strljoin() behavior when string fits buffer without truncation. - * The return value of each call should be the length of the resulting string. - * Each resulting string should contain the full result of the join operation, - * including null terminator. + * Verify guac_strljoin() behavior when the string fits the buffer without + * truncation. The return value of each call should be the length of the + * resulting string. Each resulting string should contain the full result of + * the join operation, including null terminator. */ void test_string__strljoin() { @@ -82,9 +82,9 @@ void test_string__strljoin() { } /** - * Verify guac_strljoin() behavior when string must be truncated to fit buffer. - * The return value of each call should be the length that would result from - * joining the strings given an infinite buffer, however only as many + * Verify guac_strljoin() behavior when the string must be truncated to fit the + * buffer. The return value of each call should be the length that would result + * from joining the strings given an infinite buffer, however only as many * characters as can fit should be appended to the string within the buffer, * and the buffer should be null-terminated. */ From ba8fd17394854cd51d458b43550d33f4dbc20802 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 18:53:06 -0800 Subject: [PATCH 091/175] GUACAMOLE-637: "concatentation" ... not "contatenation". --- src/libguac/tests/string/strlcat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libguac/tests/string/strlcat.c b/src/libguac/tests/string/strlcat.c index fb0c62aa..cf3dc330 100644 --- a/src/libguac/tests/string/strlcat.c +++ b/src/libguac/tests/string/strlcat.c @@ -27,7 +27,7 @@ * Verify guac_strlcat() behavior when the string fits the buffer without * truncation. The return value of each call should be the length of the * resulting string. Each resulting string should contain the full result of - * the contatenation, including null terminator. + * the concatenation, including null terminator. */ void test_string__strlcat() { From 350d8e5995035435a96cfae288dbfe0f41d841b7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 19:02:19 -0800 Subject: [PATCH 092/175] GUACAMOLE-637: Document failsafe behavior of guac_strlcat() in the event the destination buffer is not terminated as required. --- src/libguac/guacamole/string.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index 479a8052..5e56e330 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -84,6 +84,12 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n); * string. This buffer MUST already be null-terminated and will always be * null-terminated unless zero bytes are available within the buffer. * + * As a safeguard against incorrectly-written code, in the event that the + * destination buffer is not null-terminated, this function will still stop + * before overrunning the buffer, instead behaving as if the length of the + * string in the buffer is exactly the size of the buffer. The destination + * buffer will remain untouched (and unterminated) in this case. + * * @param src * The source string to append to the the destination buffer. This string * MUST be null-terminated. From c6feef6c869c1b51d585e292296ebb3dfcfd2c9d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 23 Jan 2019 20:28:09 -0800 Subject: [PATCH 093/175] GUACAMOLE-637: Clarify purpose of initial empty path component. Fix normalization logic to ensure that empty component is always present. --- src/protocols/rdp/rdp_fs.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index 3319e1db..6b9bf044 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -608,11 +608,15 @@ const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) { int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { int i; - int path_depth = 1; - char path_component_data[GUAC_RDP_FS_MAX_PATH]; - const char* path_components[GUAC_RDP_MAX_PATH_DEPTH] = { "" }; - const char* current_path_component_data = &(path_component_data[0]); + char path_component_data[GUAC_RDP_FS_MAX_PATH]; + const char* current_path_component_data = &(path_component_data[0]); + + /* Always include a blank path component at the beginning, such that the + * eventual call to guac_strljoin() will produce an absolute path (leading + * backslash) */ + int path_depth = 1; + const char* path_components[GUAC_RDP_MAX_PATH_DEPTH] = { "" }; /* If original path is not absolute, normalization fails */ if (path[0] != '\\' && path[0] != '/') @@ -641,7 +645,7 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { /* If component refers to parent, just move up in depth */ if (strcmp(current_path_component_data, "..") == 0) { - if (path_depth > 0) + if (path_depth > 1) path_depth--; } From 193f721c7bd365dcbb7debca8bbfda7a1c4fa809 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 9 Feb 2019 11:18:26 -0800 Subject: [PATCH 094/175] GUACAMOLE-729: Bump version number to 1.1.0. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b63ed690..0acb27b1 100644 --- a/configure.ac +++ b/configure.ac @@ -18,7 +18,7 @@ # AC_PREREQ([2.61]) -AC_INIT([guacamole-server], [1.0.0]) +AC_INIT([guacamole-server], [1.1.0]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AM_SILENT_RULES([yes]) From a0d030a7aed2a47f2c0fbc8dcdc80b6a0d41acd9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 9 Feb 2019 11:20:06 -0800 Subject: [PATCH 095/175] GUACAMOLE-729: Update libtool version info for libguac (interfaces added and changed). --- src/libguac/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 2757f706..16b0e2c2 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -125,7 +125,7 @@ libguac_la_CFLAGS = \ -Werror -Wall -pedantic -I$(srcdir)/guacamole libguac_la_LDFLAGS = \ - -version-info 16:0:0 \ + -version-info 17:0:0 \ -no-undefined \ @CAIRO_LIBS@ \ @DL_LIBS@ \ From cb227cc3a158938b46bff41ae3b765988547be62 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 9 Feb 2019 11:38:16 -0800 Subject: [PATCH 096/175] GUACAMOLE-729: Bump version number of guacctl. --- bin/guacctl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/guacctl b/bin/guacctl index 2d6d2637..3d262093 100755 --- a/bin/guacctl +++ b/bin/guacctl @@ -117,7 +117,7 @@ error() { ## usage() { cat >&2 < Date: Tue, 19 Feb 2019 11:34:18 -0800 Subject: [PATCH 097/175] GUACAMOLE-381: Add copy/paste disable flags for all supported protocols. --- src/protocols/kubernetes/settings.c | 26 ++++++++++++++++++++++++++ src/protocols/kubernetes/settings.h | 14 ++++++++++++++ src/protocols/rdp/rdp_settings.c | 26 ++++++++++++++++++++++++++ src/protocols/rdp/rdp_settings.h | 14 ++++++++++++++ src/protocols/ssh/settings.c | 26 ++++++++++++++++++++++++++ src/protocols/ssh/settings.h | 14 ++++++++++++++ src/protocols/telnet/settings.c | 26 ++++++++++++++++++++++++++ src/protocols/telnet/settings.h | 14 ++++++++++++++ src/protocols/vnc/settings.c | 27 ++++++++++++++++++++++++++- src/protocols/vnc/settings.h | 14 ++++++++++++++ 10 files changed, 200 insertions(+), 1 deletion(-) diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 4f00a445..ec23880b 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -50,6 +50,8 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { "read-only", "backspace", "scrollback", + "disable-copy", + "disable-paste", NULL }; @@ -216,6 +218,20 @@ enum KUBERNETES_ARGS_IDX { */ IDX_SCROLLBACK, + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the terminal to the client + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the terminal using + * the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + KUBERNETES_ARGS_COUNT }; @@ -364,6 +380,16 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_BACKSPACE, 127); + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + /* Parsing was successful */ return settings; diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index 6267a18b..eef4973e 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -170,6 +170,20 @@ typedef struct guac_kubernetes_settings { */ int resolution; + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the terminal to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the terminal using the + * clipboard. + */ + bool disable_paste; + /** * The path in which the typescript should be saved, if enabled. If no * typescript should be saved, this will be NULL. diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 88d9bc7f..d46cd27c 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -118,6 +118,8 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "load-balance-info", #endif + "disable-copy", + "disable-paste", NULL }; @@ -545,6 +547,20 @@ enum RDP_ARGS_IDX { IDX_LOAD_BALANCE_INFO, #endif + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the remote desktop to the + * client using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the remote desktop + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + RDP_ARGS_COUNT }; @@ -1007,6 +1023,16 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, IDX_LOAD_BALANCE_INFO, NULL); #endif + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, 0); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, 0); + /* Success */ return settings; diff --git a/src/protocols/rdp/rdp_settings.h b/src/protocols/rdp/rdp_settings.h index 6955ed58..9edbedeb 100644 --- a/src/protocols/rdp/rdp_settings.h +++ b/src/protocols/rdp/rdp_settings.h @@ -268,6 +268,20 @@ typedef struct guac_rdp_settings { */ char** svc_names; + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the remote desktop to the client using the + * clipboard. + */ + int disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the remote desktop using + * the clipboard. + */ + int disable_paste; + /** * Whether the desktop wallpaper should be visible. If unset, the desktop * wallpaper will be hidden, reducing the amount of bandwidth required. diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 962524ce..84364193 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -62,6 +62,8 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { "scrollback", "locale", "timezone", + "disable-copy", + "disable-paste", NULL }; @@ -258,6 +260,20 @@ enum SSH_ARGS_IDX { */ IDX_TIMEZONE, + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the terminal to the client + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the terminal using + * the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + SSH_ARGS_COUNT }; @@ -426,6 +442,16 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_TIMEZONE, NULL); + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + /* Parsing was successful */ return settings; diff --git a/src/protocols/ssh/settings.h b/src/protocols/ssh/settings.h index baa634ad..bab21bdf 100644 --- a/src/protocols/ssh/settings.h +++ b/src/protocols/ssh/settings.h @@ -155,6 +155,20 @@ typedef struct guac_ssh_settings { */ int resolution; + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the terminal to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the terminal using the + * clipboard. + */ + bool disable_paste; + /** * Whether SFTP is enabled. */ diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 890d5fec..ded5c4f3 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -55,6 +55,8 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { "scrollback", "login-success-regex", "login-failure-regex", + "disable-copy", + "disable-paste", NULL }; @@ -216,6 +218,20 @@ enum TELNET_ARGS_IDX { */ IDX_LOGIN_FAILURE_REGEX, + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the terminal to the client + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the terminal using + * the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + TELNET_ARGS_COUNT }; @@ -428,6 +444,16 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv, IDX_TERMINAL_TYPE, "linux"); + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + /* Parsing was successful */ return settings; diff --git a/src/protocols/telnet/settings.h b/src/protocols/telnet/settings.h index 86302b77..691669cb 100644 --- a/src/protocols/telnet/settings.h +++ b/src/protocols/telnet/settings.h @@ -171,6 +171,20 @@ typedef struct guac_telnet_settings { */ int resolution; + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the terminal to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the terminal using the + * clipboard. + */ + bool disable_paste; + /** * The path in which the typescript should be saved, if enabled. If no * typescript should be saved, this will be NULL. diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 8f65cfb2..3e0ebc6b 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -77,7 +77,8 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "recording-exclude-mouse", "recording-include-keys", "create-recording-path", - + "disable-copy", + "disable-paste", NULL }; @@ -298,6 +299,20 @@ enum VNC_ARGS_IDX { */ IDX_CREATE_RECORDING_PATH, + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the remote desktop to the + * client using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the remote desktop + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + VNC_ARGS_COUNT }; @@ -493,6 +508,16 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_CREATE_RECORDING_PATH, false); + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + return settings; } diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 3e2ebd5e..13a3d87b 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -127,6 +127,20 @@ typedef struct guac_vnc_settings { */ char* clipboard_encoding; + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the remote desktop to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the remote desktop using + * the clipboard. + */ + bool disable_paste; + #ifdef ENABLE_COMMON_SSH /** * Whether SFTP should be enabled for the VNC connection. From 993d5c5707f7b23b7f97da5845804a811a626b37 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 19 Feb 2019 11:40:32 -0800 Subject: [PATCH 098/175] GUACAMOLE-381: Disable inbound clipboard transfer if "disable-paste" is set. --- src/protocols/kubernetes/user.c | 11 +++++++---- src/protocols/rdp/user.c | 11 +++++++---- src/protocols/ssh/user.c | 11 +++++++---- src/protocols/telnet/user.c | 11 +++++++---- src/protocols/vnc/user.c | 11 +++++++---- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c index f90260e7..f4e9eb44 100644 --- a/src/protocols/kubernetes/user.c +++ b/src/protocols/kubernetes/user.c @@ -79,10 +79,13 @@ int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { /* Only handle events if not read-only */ if (!settings->read_only) { - /* General mouse/keyboard/clipboard events */ - user->key_handler = guac_kubernetes_user_key_handler; - user->mouse_handler = guac_kubernetes_user_mouse_handler; - user->clipboard_handler = guac_kubernetes_clipboard_handler; + /* General mouse/keyboard events */ + user->key_handler = guac_kubernetes_user_key_handler; + user->mouse_handler = guac_kubernetes_user_mouse_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_kubernetes_clipboard_handler; /* STDIN redirection */ user->pipe_handler = guac_kubernetes_pipe_handler; diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index 6aa71ae9..025848aa 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -97,10 +97,13 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { /* Only handle events if not read-only */ if (!settings->read_only) { - /* General mouse/keyboard/clipboard events */ - user->mouse_handler = guac_rdp_user_mouse_handler; - user->key_handler = guac_rdp_user_key_handler; - user->clipboard_handler = guac_rdp_clipboard_handler; + /* General mouse/keyboard events */ + user->mouse_handler = guac_rdp_user_mouse_handler; + user->key_handler = guac_rdp_user_key_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_rdp_clipboard_handler; /* Display size change events */ user->size_handler = guac_rdp_user_size_handler; diff --git a/src/protocols/ssh/user.c b/src/protocols/ssh/user.c index 97ed87c6..76a2a963 100644 --- a/src/protocols/ssh/user.c +++ b/src/protocols/ssh/user.c @@ -80,10 +80,13 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) { /* Only handle events if not read-only */ if (!settings->read_only) { - /* General mouse/keyboard/clipboard events */ - user->key_handler = guac_ssh_user_key_handler; - user->mouse_handler = guac_ssh_user_mouse_handler; - user->clipboard_handler = guac_ssh_clipboard_handler; + /* General mouse/keyboard events */ + user->key_handler = guac_ssh_user_key_handler; + user->mouse_handler = guac_ssh_user_mouse_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_ssh_clipboard_handler; /* STDIN redirection */ user->pipe_handler = guac_ssh_pipe_handler; diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c index 44ef9b5e..5c15283b 100644 --- a/src/protocols/telnet/user.c +++ b/src/protocols/telnet/user.c @@ -79,10 +79,13 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { /* Only handle events if not read-only */ if (!settings->read_only) { - /* General mouse/keyboard/clipboard events */ - user->key_handler = guac_telnet_user_key_handler; - user->mouse_handler = guac_telnet_user_mouse_handler; - user->clipboard_handler = guac_telnet_clipboard_handler; + /* General mouse/keyboard events */ + user->key_handler = guac_telnet_user_key_handler; + user->mouse_handler = guac_telnet_user_mouse_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_telnet_clipboard_handler; /* STDIN redirection */ user->pipe_handler = guac_telnet_pipe_handler; diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index da3b843f..0dee5043 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -91,10 +91,13 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { /* Only handle events if not read-only */ if (!settings->read_only) { - /* General mouse/keyboard/clipboard events */ - user->mouse_handler = guac_vnc_user_mouse_handler; - user->key_handler = guac_vnc_user_key_handler; - user->clipboard_handler = guac_vnc_clipboard_handler; + /* General mouse/keyboard events */ + user->mouse_handler = guac_vnc_user_mouse_handler; + user->key_handler = guac_vnc_user_key_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_vnc_clipboard_handler; #ifdef ENABLE_COMMON_SSH /* Set generic (non-filesystem) file upload handler */ From 254615509a9b809041124f8c0c9bf63aa6bb85b8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 19 Feb 2019 11:45:30 -0800 Subject: [PATCH 099/175] GUACAMOLE-381: Disable or ignore outbound clipboard transfers for VNC/RDP if "disable-copy" is set. --- src/protocols/rdp/rdp.c | 8 +++++--- src/protocols/rdp/rdp_cliprdr.c | 4 ++++ src/protocols/vnc/clipboard.c | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 042d3893..5b351c5b 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -240,11 +240,13 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { guac_rdp_audio_load_plugin(instance->context, dvc_list); } - /* Load clipboard plugin */ - if (freerdp_channels_load_plugin(channels, instance->settings, - "cliprdr", NULL)) + /* Load clipboard plugin if not disabled */ + if (!(settings->disable_copy && settings->disable_paste) + && freerdp_channels_load_plugin(channels, instance->settings, + "cliprdr", NULL)) { guac_client_log(client, GUAC_LOG_WARNING, "Failed to load cliprdr plugin. Clipboard will not work."); + } /* If RDPSND/RDPDR required, load them */ if (settings->printing_enabled diff --git a/src/protocols/rdp/rdp_cliprdr.c b/src/protocols/rdp/rdp_cliprdr.c index 53019115..903752c8 100644 --- a/src/protocols/rdp/rdp_cliprdr.c +++ b/src/protocols/rdp/rdp_cliprdr.c @@ -230,6 +230,10 @@ void guac_rdp_process_cb_data_response(guac_client* client, guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH]; + /* Ignore received text if outbound clipboard transfer is disabled */ + if (rdp_client->settings->disable_copy) + return; + guac_iconv_read* reader; const char* input = (char*) event->data; char* output = received_data; diff --git a/src/protocols/vnc/clipboard.c b/src/protocols/vnc/clipboard.c index a49f5651..c3b1bd1f 100644 --- a/src/protocols/vnc/clipboard.c +++ b/src/protocols/vnc/clipboard.c @@ -125,6 +125,10 @@ void guac_vnc_cut_text(rfbClient* client, const char* text, int textlen) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + /* Ignore received text if outbound clipboard transfer is disabled */ + if (vnc_client->settings->disable_copy) + return; + char received_data[GUAC_VNC_CLIPBOARD_MAX_LENGTH]; const char* input = text; From 228cea4af10750238b40d6b3f98fba4b00558f69 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 19 Feb 2019 12:11:14 -0800 Subject: [PATCH 100/175] GUACAMOLE-381: Disable outbound transfers from terminal protocols if "disable-copy" is set. --- src/protocols/kubernetes/kubernetes.c | 2 +- src/protocols/ssh/ssh.c | 7 ++++--- src/protocols/telnet/telnet.c | 2 +- src/terminal/select.c | 6 ++++-- src/terminal/terminal.c | 5 +++-- src/terminal/terminal/terminal.h | 18 ++++++++++++++++-- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 7c22c420..931f8ca3 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -237,7 +237,7 @@ void* guac_kubernetes_client_thread(void* data) { /* Create terminal */ kubernetes_client->term = guac_terminal_create(client, - kubernetes_client->clipboard, + kubernetes_client->clipboard, settings->disable_copy, settings->max_scrollback, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, settings->color_scheme, settings->backspace); diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 9db545bf..777172ec 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -207,9 +207,10 @@ void* ssh_client_thread(void* data) { /* Create terminal */ ssh_client->term = guac_terminal_create(client, ssh_client->clipboard, - settings->max_scrollback, settings->font_name, settings->font_size, - settings->resolution, settings->width, settings->height, - settings->color_scheme, settings->backspace); + settings->disable_copy, settings->max_scrollback, + settings->font_name, settings->font_size, settings->resolution, + settings->width, settings->height, settings->color_scheme, + settings->backspace); /* Fail if terminal init failed */ if (ssh_client->term == NULL) { diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 17abd681..d8fc5e09 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -568,7 +568,7 @@ void* guac_telnet_client_thread(void* data) { /* Create terminal */ telnet_client->term = guac_terminal_create(client, - telnet_client->clipboard, + telnet_client->clipboard, settings->disable_copy, settings->max_scrollback, settings->font_name, settings->font_size, settings->resolution, settings->width, settings->height, settings->color_scheme, settings->backspace); diff --git a/src/terminal/select.c b/src/terminal/select.c index 20fc3cda..0a075ebd 100644 --- a/src/terminal/select.c +++ b/src/terminal/select.c @@ -375,8 +375,10 @@ void guac_terminal_select_end(guac_terminal* terminal) { } /* Send data */ - guac_common_clipboard_send(terminal->clipboard, client); - guac_socket_flush(socket); + if (!terminal->disable_copy) { + guac_common_clipboard_send(terminal->clipboard, client); + guac_socket_flush(socket); + } guac_terminal_notify(terminal); diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index cf70f92d..7ddcb2f1 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -306,8 +306,8 @@ void* guac_terminal_thread(void* data) { } guac_terminal* guac_terminal_create(guac_client* client, - guac_common_clipboard* clipboard, int max_scrollback, - const char* font_name, int font_size, int dpi, + guac_common_clipboard* clipboard, bool disable_copy, + int max_scrollback, const char* font_name, int font_size, int dpi, int width, int height, const char* color_scheme, const int backspace) { @@ -404,6 +404,7 @@ guac_terminal* guac_terminal_create(guac_client* client, term->current_attributes = default_char.attributes; term->default_char = default_char; term->clipboard = clipboard; + term->disable_copy = disable_copy; /* Calculate character size */ int rows = height / term->display->char_height; diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index ac1217eb..28e69357 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -511,6 +511,14 @@ struct guac_terminal { */ char backspace; + /** + * Whether copying from the terminal clipboard should be blocked. If set, + * the contents of the terminal can still be copied, but will be usable + * only within the terminal itself. The clipboard contents will not be + * automatically streamed to the client. + */ + bool disable_copy; + }; /** @@ -533,6 +541,12 @@ struct guac_terminal { * clipboard instructions. This clipboard will not be automatically * freed when this terminal is freed. * + * @param disable_copy + * Whether copying from the terminal clipboard should be blocked. If set, + * the contents of the terminal can still be copied, but will be usable + * only within the terminal itself. The clipboard contents will not be + * automatically streamed to the client. + * * @param max_scrollback * The maximum number of rows to allow within the scrollback buffer. The * user may still alter the size of the scrollback buffer using terminal @@ -575,8 +589,8 @@ struct guac_terminal { * which renders all text to the given client. */ guac_terminal* guac_terminal_create(guac_client* client, - guac_common_clipboard* clipboard, int max_scrollback, - const char* font_name, int font_size, int dpi, + guac_common_clipboard* clipboard, bool disable_copy, + int max_scrollback, const char* font_name, int font_size, int dpi, int width, int height, const char* color_scheme, const int backspace); From 1300b64bb94a50e6926c987a6eb8086be12a0df0 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 22 Feb 2019 04:59:09 -0500 Subject: [PATCH 101/175] GUACAMOLE-694: Add ca-certificates packages as runtime dependency. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index d903c4a4..f5f7a425 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,6 +90,7 @@ ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib ENV GUACD_LOG_LEVEL=info ARG RUNTIME_DEPENDENCIES=" \ + ca-certificates \ ghostscript \ libfreerdp-plugins-standard \ fonts-liberation \ From e9a10d66b79c9fc0723dd35c1001d133e796efff Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 9 Mar 2019 17:56:55 -0500 Subject: [PATCH 102/175] GUACAMOLE-414: Add pthread lock and callbacks for TLS write locking. --- src/protocols/vnc/vnc.c | 64 ++++++++++++++++++++++++++++++++++++++++- src/protocols/vnc/vnc.h | 5 ++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index d9f9dbbb..d13826d1 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -55,6 +55,62 @@ char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; +/** + * A callback function that is called by the VNC library prior to writing + * data to a TLS-encrypted socket. This returns the rfbBool FALSE value + * if there's an error locking the mutex, or rfbBool TRUE otherwise. + * + * @param rfb_client + * The rfbClient for which to lock the TLS mutex. + * + * @returns + * rfbBool FALSE if an error occurs locking the mutex, otherwise + * TRUE. + */ +static rfbBool guac_vnc_lock_write_to_tls(rfbClient* rfb_client) { + + // Retrieve the Guacamole data structures + guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + // Lock write access + int retval = pthread_mutex_lock(&(vnc_client->tls_lock)); + if (retval) { + guac_client_log(gc, GUAC_LOG_ERROR, "Error locking TLS write mutex: %d", retval); + return FALSE; + } + return TRUE; + +} + +/** + * A callback function for use by the VNC library that is called once + * the client is finished writing to a TLS-encrypted socket. A rfbBool + * FALSE value is returned if an error occurs unlocking the mutex, + * otherwise TRUE is returned. + * + * @param rfb_client + * The rfbClient for which to unlock the TLS mutex. + * + * @returns + * rfbBool FALSE if an error occurs unlocking the mutex, otherwise + * TRUE. + */ +static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { + + // Retrieve the Guacamole data structures + guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; + + // Unlock write access + int retval = pthread_mutex_unlock(&(vnc_client->tls_lock)); + if (retval) { + guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %d", retval); + return FALSE; + } + return TRUE; +} + rfbClient* guac_vnc_get_client(guac_client* client) { rfbClient* rfb_client = rfbGetClient(8, 3, 4); /* 32-bpp client */ @@ -68,6 +124,10 @@ rfbClient* guac_vnc_get_client(guac_client* client) { rfb_client->GotFrameBufferUpdate = guac_vnc_update; rfb_client->GotCopyRect = guac_vnc_copyrect; + /* TLS Locking and Unlocking */ + rfb_client->LockWriteToTLS = guac_vnc_lock_write_to_tls; + rfb_client->UnlockWriteToTLS = guac_vnc_unlock_write_to_tls; + /* Do not handle clipboard and local cursor if read-only */ if (vnc_settings->read_only == 0) { @@ -182,6 +242,9 @@ void* guac_vnc_client_thread(void* data) { rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error; + /* Initialize the write lock */ + pthread_mutex_init(&(vnc_client->tls_lock), NULL); + /* Attempt connection */ rfbClient* rfb_client = guac_vnc_get_client(client); int retries_remaining = settings->retries; @@ -403,4 +466,3 @@ void* guac_vnc_client_thread(void* data) { return NULL; } - diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index ce2d20af..31e78461 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -55,6 +55,11 @@ typedef struct guac_vnc_client { */ pthread_t client_thread; + /** + * The TLS mutex lock for the client. + */ + pthread_mutex_t tls_lock; + /** * The underlying VNC client. */ From c90c057e1276cc92f6143eafc6f5aef9bdbd5ca5 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 9 Mar 2019 21:39:56 -0500 Subject: [PATCH 103/175] GUACAMOLE-414: Add version checks for TLS locking. --- src/protocols/vnc/vnc.c | 7 +++++++ src/protocols/vnc/vnc.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index d13826d1..54e88dca 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,7 @@ char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; +#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 /** * A callback function that is called by the VNC library prior to writing * data to a TLS-encrypted socket. This returns the rfbBool FALSE value @@ -110,6 +112,7 @@ static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { } return TRUE; } +#endif rfbClient* guac_vnc_get_client(guac_client* client) { @@ -124,9 +127,11 @@ rfbClient* guac_vnc_get_client(guac_client* client) { rfb_client->GotFrameBufferUpdate = guac_vnc_update; rfb_client->GotCopyRect = guac_vnc_copyrect; +#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 /* TLS Locking and Unlocking */ rfb_client->LockWriteToTLS = guac_vnc_lock_write_to_tls; rfb_client->UnlockWriteToTLS = guac_vnc_unlock_write_to_tls; +#endif /* Do not handle clipboard and local cursor if read-only */ if (vnc_settings->read_only == 0) { @@ -242,8 +247,10 @@ void* guac_vnc_client_thread(void* data) { rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error; +#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 /* Initialize the write lock */ pthread_mutex_init(&(vnc_client->tls_lock), NULL); +#endif /* Attempt connection */ rfbClient* rfb_client = guac_vnc_get_client(client); diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index 31e78461..de9b3239 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef ENABLE_PULSE #include "pulse/pulse.h" @@ -55,10 +56,12 @@ typedef struct guac_vnc_client { */ pthread_t client_thread; +#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 /** * The TLS mutex lock for the client. */ pthread_mutex_t tls_lock; +#endif /** * The underlying VNC client. From df4c93b3e8e25fde155c07d07ba8972a7455ff54 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 10 Mar 2019 15:22:49 -0400 Subject: [PATCH 104/175] GUACAMOLE-414: Use configure checks for finding TLS locking support. --- configure.ac | 27 +++++++++++++++++++++++++++ src/protocols/vnc/vnc.c | 6 +++--- src/protocols/vnc/vnc.h | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 0acb27b1..85a0559e 100644 --- a/configure.ac +++ b/configure.ac @@ -512,6 +512,33 @@ then fi +# +# TLS Locking Support within libVNCServer +# + +if test "x${have_libvncserver}" = "xyes" +then + + have_vnc_tls_locking=yes + AC_CHECK_MEMBERS([rfbClient.LockWriteToTLS, rfbClient.UnlockWriteToTLS], + [], [have_vnc_tls_locking=no], + [[#include ]]) + + if test "x${have_vnc_tls_locking}" = "xno" + then + AC_MSG_WARN([ + -------------------------------------------- + This version of libvncclient lacks support + for TLS locking. VNC connections that use + TLS may experience instability as documented + in GUACAMOLE-414]) + else + AC_DEFINE([ENABLE_VNC_TLS_LOCKING],, + [Whether support for TLS locking within VNC is enabled.]) + fi + +fi + # # FreeRDP # diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 54e88dca..932a917d 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -56,7 +56,7 @@ char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; -#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 +#ifdef ENABLE_VNC_TLS_LOCKING /** * A callback function that is called by the VNC library prior to writing * data to a TLS-encrypted socket. This returns the rfbBool FALSE value @@ -127,7 +127,7 @@ rfbClient* guac_vnc_get_client(guac_client* client) { rfb_client->GotFrameBufferUpdate = guac_vnc_update; rfb_client->GotCopyRect = guac_vnc_copyrect; -#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 +#ifdef ENABLE_VNC_TLS_LOCKING /* TLS Locking and Unlocking */ rfb_client->LockWriteToTLS = guac_vnc_lock_write_to_tls; rfb_client->UnlockWriteToTLS = guac_vnc_unlock_write_to_tls; @@ -247,7 +247,7 @@ void* guac_vnc_client_thread(void* data) { rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error; -#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 +#ifdef ENABLE_VNC_TLS_LOCKING /* Initialize the write lock */ pthread_mutex_init(&(vnc_client->tls_lock), NULL); #endif diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index de9b3239..7e927585 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -56,7 +56,7 @@ typedef struct guac_vnc_client { */ pthread_t client_thread; -#if LIBVNCSERVER_VERSION_MAJOR >=0 && LIBVNCSERVER_VERSION_MINOR >= 9 && LIBVNCSERVER_VERSION_PATCHLEVEL >= 11 +#ifdef ENABLE_VNC_TLS_LOCKING /** * The TLS mutex lock for the client. */ From 36817f37741bdc7e5ce6e9ad2e5e7bc044720588 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 10 Mar 2019 17:33:14 -0400 Subject: [PATCH 105/175] GUACAMOLE-414: Clean up style and move mutex init to client allocation. --- src/protocols/vnc/client.c | 11 +++++++++++ src/protocols/vnc/vnc.c | 14 ++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index 9cc85a18..cee1d4d5 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -36,6 +36,7 @@ #include +#include #include #include @@ -48,6 +49,11 @@ int guac_client_init(guac_client* client) { guac_vnc_client* vnc_client = calloc(1, sizeof(guac_vnc_client)); client->data = vnc_client; +#ifdef ENABLE_VNC_TLS_LOCKING + /* Initialize the write lock */ + pthread_mutex_init(&(vnc_client->tls_lock), NULL); +#endif + /* Init clipboard */ vnc_client->clipboard = guac_common_clipboard_alloc(GUAC_VNC_CLIPBOARD_MAX_LENGTH); @@ -125,6 +131,11 @@ int guac_vnc_client_free_handler(guac_client* client) { if (settings != NULL) guac_vnc_settings_free(settings); +#ifdef ENABLE_VNC_TLS_LOCKING + /* Clean up TLS lock mutex. */ + pthread_mutex_destroy(&(vnc_client->tls_lock)); +#endif + /* Free generic data struct */ free(client->data); diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 932a917d..c68f6d3b 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -47,7 +47,6 @@ #include #include #include -#include #include #include @@ -71,11 +70,11 @@ char* GUAC_VNC_CLIENT_KEY = "GUAC_VNC"; */ static rfbBool guac_vnc_lock_write_to_tls(rfbClient* rfb_client) { - // Retrieve the Guacamole data structures + /* Retrieve the Guacamole data structures */ guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; - // Lock write access + /* Lock write access */ int retval = pthread_mutex_lock(&(vnc_client->tls_lock)); if (retval) { guac_client_log(gc, GUAC_LOG_ERROR, "Error locking TLS write mutex: %d", retval); @@ -100,11 +99,11 @@ static rfbBool guac_vnc_lock_write_to_tls(rfbClient* rfb_client) { */ static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { - // Retrieve the Guacamole data structures + /* Retrieve the Guacamole data structures */ guac_client* gc = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY); guac_vnc_client* vnc_client = (guac_vnc_client*) gc->data; - // Unlock write access + /* Unlock write access */ int retval = pthread_mutex_unlock(&(vnc_client->tls_lock)); if (retval) { guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %d", retval); @@ -247,11 +246,6 @@ void* guac_vnc_client_thread(void* data) { rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error; -#ifdef ENABLE_VNC_TLS_LOCKING - /* Initialize the write lock */ - pthread_mutex_init(&(vnc_client->tls_lock), NULL); -#endif - /* Attempt connection */ rfbClient* rfb_client = guac_vnc_get_client(client); int retries_remaining = settings->retries; From bfc6c1e6e0e9852725bb9d4bdd49b7f5ac4536d6 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 10 Mar 2019 17:40:34 -0400 Subject: [PATCH 106/175] GUACAMOLE-414: Convert errors to strings from ptread_mutex_lock and unlock. --- src/protocols/vnc/vnc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index c68f6d3b..66de2634 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -77,7 +77,8 @@ static rfbBool guac_vnc_lock_write_to_tls(rfbClient* rfb_client) { /* Lock write access */ int retval = pthread_mutex_lock(&(vnc_client->tls_lock)); if (retval) { - guac_client_log(gc, GUAC_LOG_ERROR, "Error locking TLS write mutex: %d", retval); + guac_client_log(gc, GUAC_LOG_ERROR, "Error locking TLS write mutex: %s", + strerror(retval)); return FALSE; } return TRUE; @@ -106,7 +107,8 @@ static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { /* Unlock write access */ int retval = pthread_mutex_unlock(&(vnc_client->tls_lock)); if (retval) { - guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %d", retval); + guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %d", + strerror(retval)); return FALSE; } return TRUE; From a6f2ab9d93fe1e5b6a4aa0b5551119965c86fba8 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 10 Mar 2019 17:41:45 -0400 Subject: [PATCH 107/175] GUACAMOLE-414: Use correct formatting for string from strerror. --- src/protocols/vnc/vnc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 66de2634..a3ae9b12 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -107,7 +107,7 @@ static rfbBool guac_vnc_unlock_write_to_tls(rfbClient* rfb_client) { /* Unlock write access */ int retval = pthread_mutex_unlock(&(vnc_client->tls_lock)); if (retval) { - guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %d", + guac_client_log(gc, GUAC_LOG_ERROR, "Error unlocking TLS write mutex: %s", strerror(retval)); return FALSE; } From a4521208ba03f7e5939203c46c71bd2b6ffbffb6 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 24 Mar 2019 15:09:58 -0400 Subject: [PATCH 108/175] GUACAMOLE-414: Remove unnecessary rfbconfig include. --- src/protocols/vnc/vnc.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h index 7e927585..7c189cfb 100644 --- a/src/protocols/vnc/vnc.h +++ b/src/protocols/vnc/vnc.h @@ -32,7 +32,6 @@ #include #include #include -#include #ifdef ENABLE_PULSE #include "pulse/pulse.h" From 1b8e31b70cf11a0b2676795ba1333b0ad43a8fae Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 10 Mar 2019 17:08:02 -0400 Subject: [PATCH 109/175] GUACAMOLE-296: Add checks for Stream functions in winpr libraries. --- configure.ac | 17 +++++++++++++++++ src/protocols/rdp/Makefile.am | 15 ++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 0acb27b1..11e765b0 100644 --- a/configure.ac +++ b/configure.ac @@ -518,6 +518,7 @@ fi have_freerdp=disabled RDP_LIBS= +WINPR_LIBS= AC_ARG_WITH([rdp], [AS_HELP_STRING([--with-rdp], [support RDP @<:@default=check@:>@])], @@ -719,6 +720,21 @@ then [#include ])]) fi +# Find location of Stream_New and Stream_free +if test "x${have_freerdp}" = "xyes" -a "x${have_winpr}" = "xyes" +then + AC_CHECK_LIB([winpr], [Stream_New, Stream_Free], + [WINPR_LIBS="$WINPR_LIBS -lwinpr"], + [AC_CHECK_LIB([winpr-utils], [Stream_New, Stream_Free], + [WINPR_LIBS="$WINPR_LIBS -lwinpr-utils"], + [AC_MSG_WARN([ + ------------------------------------------ + Unable to locate stream functions in winpr + libraries. RDP will be disabled. + ------------------------------------------]) + have_freerdp=no])]) +fi + # Check for types in WinPR if test "x${have_freerdp}" = "xyes" then @@ -1027,6 +1043,7 @@ AM_CONDITIONAL([ENABLE_WINPR], [test "x${have_winpr}" = "xyes"]) AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp}" = "xyes"]) AC_SUBST(RDP_LIBS) +AC_SUBST(WINPR_LIBS) # # libssh2 diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 29a9674b..c3f94442 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -155,7 +155,8 @@ libguac_client_rdp_la_LDFLAGS = \ -version-info 0:0:0 \ @CAIRO_LIBS@ \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @WINPR_LIBS@ libguac_client_rdp_la_LIBADD = \ @COMMON_LTLIB@ \ @@ -174,7 +175,8 @@ guacdr_cflags = \ guacdr_ldflags = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @WINPR_LIBS@ guacdr_libadd = \ @COMMON_LTLIB@ \ @@ -193,7 +195,8 @@ guacai_cflags = \ guacai_ldflags = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @WINPR_LIBS@ guacai_libadd = \ @COMMON_LTLIB@ \ @@ -212,7 +215,8 @@ guacsnd_cflags = \ guacsnd_ldflags = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @WINPR_LIBS@ guacsnd_libadd = \ @COMMON_LTLIB@ \ @@ -231,7 +235,8 @@ guacsvc_cflags = \ guacsvc_ldflags = \ -module -avoid-version -shared \ @PTHREAD_LIBS@ \ - @RDP_LIBS@ + @RDP_LIBS@ \ + @WINPR_LIBS@ guacsvc_libadd = \ @COMMON_LTLIB@ \ From 24ab5ca85b4adfd26c1a489e4c943208b8bc0860 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 11:49:51 -0700 Subject: [PATCH 110/175] GUACAMOLE-637: Remove unnecessary .gitignore files. Universally exclude test output. Remove duplicated exclusions. --- .gitignore | 4 ++++ src/common/.gitignore | 4 ---- src/guacd/.gitignore | 35 ----------------------------- src/libguac/.gitignore | 4 ---- src/protocols/rdp/.gitignore | 39 --------------------------------- src/protocols/ssh/.gitignore | 36 ------------------------------ src/protocols/telnet/.gitignore | 36 ------------------------------ src/protocols/vnc/.gitignore | 36 ------------------------------ 8 files changed, 4 insertions(+), 190 deletions(-) delete mode 100644 src/protocols/rdp/.gitignore delete mode 100644 src/protocols/ssh/.gitignore delete mode 100644 src/protocols/telnet/.gitignore delete mode 100644 src/protocols/vnc/.gitignore diff --git a/.gitignore b/.gitignore index e0a6d53e..e716ee0f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ *.gcov *.gcno +# Test suite output +*.log +*.trs + # Backup files *~ diff --git a/src/common/.gitignore b/src/common/.gitignore index f7efbda8..b66a6a9a 100644 --- a/src/common/.gitignore +++ b/src/common/.gitignore @@ -3,7 +3,3 @@ _generated_runner.c test_common -# Test suite output -*.log -*.trs - diff --git a/src/guacd/.gitignore b/src/guacd/.gitignore index 558c3194..4f9ae75e 100644 --- a/src/guacd/.gitignore +++ b/src/guacd/.gitignore @@ -13,38 +13,3 @@ guacd.exe man/guacd.8 man/guacd.conf.5 -# Object code -*.o -*.so -*.lo -*.la - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/ -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing - - diff --git a/src/libguac/.gitignore b/src/libguac/.gitignore index ab580797..9adc856b 100644 --- a/src/libguac/.gitignore +++ b/src/libguac/.gitignore @@ -3,7 +3,3 @@ _generated_runner.c test_libguac -# Test suite output -*.log -*.trs - diff --git a/src/protocols/rdp/.gitignore b/src/protocols/rdp/.gitignore deleted file mode 100644 index dd8ff144..00000000 --- a/src/protocols/rdp/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ - -# Object code -*.o -*.so -*.lo -*.la - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/* -!README -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing - -# Autogenerated sources -_generated_keymaps.c - diff --git a/src/protocols/ssh/.gitignore b/src/protocols/ssh/.gitignore deleted file mode 100644 index 3f725332..00000000 --- a/src/protocols/ssh/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ - -# Object code -*.o -*.so -*.lo -*.la - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/* -!README -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing - diff --git a/src/protocols/telnet/.gitignore b/src/protocols/telnet/.gitignore deleted file mode 100644 index 3f725332..00000000 --- a/src/protocols/telnet/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ - -# Object code -*.o -*.so -*.lo -*.la - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/* -!README -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing - diff --git a/src/protocols/vnc/.gitignore b/src/protocols/vnc/.gitignore deleted file mode 100644 index 3f725332..00000000 --- a/src/protocols/vnc/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ - -# Object code -*.o -*.so -*.lo -*.la - -# Backup files -*~ - -# Release files -*.tar.gz - -# Files currently being edited by vim or vi -*.swp - -# automake/autoconf -.deps/ -.libs/ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache/ -m4/* -!README -config.guess -config.log -config.status -config.sub -configure -depcomp -install-sh -libtool -ltmain.sh -missing - From f19754cfa6be8fde5012a77f1c8cc77500a74018 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 13:41:24 -0700 Subject: [PATCH 111/175] GUACAMOLE-637: Add unit tests for SFTP path normalization. --- configure.ac | 1 + src/common-ssh/.gitignore | 5 + src/common-ssh/Makefile.am | 1 + src/common-ssh/common-ssh/sftp.h | 25 +++ src/common-ssh/sftp.c | 24 +-- src/common-ssh/tests/Makefile.am | 66 +++++++ src/common-ssh/tests/sftp/normalize_path.c | 216 +++++++++++++++++++++ 7 files changed, 315 insertions(+), 23 deletions(-) create mode 100644 src/common-ssh/.gitignore create mode 100644 src/common-ssh/tests/Makefile.am create mode 100644 src/common-ssh/tests/sftp/normalize_path.c diff --git a/configure.ac b/configure.ac index 7ea60298..13231239 100644 --- a/configure.ac +++ b/configure.ac @@ -1317,6 +1317,7 @@ AC_CONFIG_FILES([Makefile src/common/Makefile src/common/tests/Makefile src/common-ssh/Makefile + src/common-ssh/tests/Makefile src/terminal/Makefile src/libguac/Makefile src/libguac/tests/Makefile diff --git a/src/common-ssh/.gitignore b/src/common-ssh/.gitignore new file mode 100644 index 00000000..f18cde53 --- /dev/null +++ b/src/common-ssh/.gitignore @@ -0,0 +1,5 @@ + +# Auto-generated test runner and binary +_generated_runner.c +test_common_ssh + diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 81167238..8402e5b0 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -27,6 +27,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 noinst_LTLIBRARIES = libguac_common_ssh.la +SUBDIRS = . tests libguac_common_ssh_la_SOURCES = \ buffer.c \ diff --git a/src/common-ssh/common-ssh/sftp.h b/src/common-ssh/common-ssh/sftp.h index 0ec2d12b..eafdf21f 100644 --- a/src/common-ssh/common-ssh/sftp.h +++ b/src/common-ssh/common-ssh/sftp.h @@ -255,5 +255,30 @@ int guac_common_ssh_sftp_handle_file_stream( void guac_common_ssh_sftp_set_upload_path( guac_common_ssh_sftp_filesystem* filesystem, const char* path); +/** + * Given an arbitrary absolute path, which may contain "..", ".", and + * backslashes, creates an equivalent absolute path which does NOT contain + * relative path components (".." or "."), backslashes, or empty path + * components. With the exception of paths referring to the root directory, the + * resulting path is guaranteed to not contain trailing slashes. + * + * Normalization will fail if the given path is not absolute, is too long, or + * contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components. + * + * @param fullpath + * The buffer to populate with the normalized path. The normalized path + * will not contain relative path components like ".." or ".", nor will it + * contain backslashes. This buffer MUST be at least + * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size. + * + * @param path + * The absolute path to normalize. + * + * @return + * Non-zero if normalization succeeded, zero otherwise. + */ +int guac_common_ssh_sftp_normalize_path(char* fullpath, + const char* path); + #endif diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c index 0a24c049..786ccc8d 100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@ -33,29 +33,7 @@ #include #include -/** - * Given an arbitrary absolute path, which may contain "..", ".", and - * backslashes, creates an equivalent absolute path which does NOT contain - * relative path components (".." or "."), backslashes, or empty path - * components. With the exception of paths referring to the root directory, the - * resulting path is guaranteed to not contain trailing slashes. - * - * Normalization will fail if the given path is not absolute, is too long, or - * contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components. - * - * @param fullpath - * The buffer to populate with the normalized path. The normalized path - * will not contain relative path components like ".." or ".", nor will it - * contain backslashes. This buffer MUST be at least - * GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size. - * - * @param path - * The absolute path to normalize. - * - * @return - * Non-zero if normalization succeeded, zero otherwise. - */ -static int guac_common_ssh_sftp_normalize_path(char* fullpath, +int guac_common_ssh_sftp_normalize_path(char* fullpath, const char* path) { int i; diff --git a/src/common-ssh/tests/Makefile.am b/src/common-ssh/tests/Makefile.am new file mode 100644 index 00000000..b26d5bbf --- /dev/null +++ b/src/common-ssh/tests/Makefile.am @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +# +# Unit tests for common SSH support +# + +check_PROGRAMS = test_common_ssh +TESTS = $(check_PROGRAMS) + +test_common_ssh_SOURCES = \ + sftp/normalize_path.c + +test_common_ssh_CFLAGS = \ + -Werror -Wall -pedantic \ + @COMMON_INCLUDE@ \ + @COMMON_SSH_INCLUDE@ + +test_common_ssh_LDADD = \ + @CUNIT_LIBS@ \ + @COMMON_SSH_LTLIB@ \ + @COMMON_LTLIB@ + +# +# Autogenerate test runner +# + +GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl +CLEANFILES = _generated_runner.c + +_generated_runner.c: $(test_common_ssh_SOURCES) + $(AM_V_GEN) $(GEN_RUNNER) $(test_common_ssh_SOURCES) > $@ + +nodist_test_common_ssh_SOURCES = \ + _generated_runner.c + +# Use automake's TAP test driver for running any tests +LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh + diff --git a/src/common-ssh/tests/sftp/normalize_path.c b/src/common-ssh/tests/sftp/normalize_path.c new file mode 100644 index 00000000..23975252 --- /dev/null +++ b/src/common-ssh/tests/sftp/normalize_path.c @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "common-ssh/sftp.h" + +#include +#include + +/** + * Test which verifies absolute Windows-style paths are correctly normalized to + * absolute paths with UNIX separators and no relative components. + */ +void test_fs__normalize_absolute_windows() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\baz\\"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\baz\\a\\..\\b"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar\\baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\..\\..\\..\\..\\baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized)); + +} + +/** + * Test which verifies absolute UNIX-style paths are correctly normalized to + * absolute paths with UNIX separators and no relative components. + */ +void test_fs__normalize_absolute_unix() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../baz/"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../baz/a/../b"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/./bar/baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../../../../../baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized)); + +} + +/** + * Test which verifies absolute paths consisting of mixed Windows and UNIX path + * separators are correctly normalized to absolute paths with UNIX separators + * and no relative components. + */ +void test_fs__normalize_absolute_mixed() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo\\bar/..\\baz/"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../../baz\\a\\..\\b"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar/baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized)); + + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../..\\..\\..\\../..\\baz"), 0); + CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized)); + +} + +/** + * Test which verifies relative Windows-style paths are always rejected. + */ +void test_fs__normalize_relative_windows() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar\\baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo\\bar\\baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo\\bar\\baz"), 0); + +} + +/** + * Test which verifies relative UNIX-style paths are always rejected. + */ +void test_fs__normalize_relative_unix() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo/bar/baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo/bar/baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo/bar/baz"), 0); + +} + +/** + * Test which verifies relative paths consisting of mixed Windows and UNIX path + * separators are always rejected. + */ +void test_fs__normalize_relative_mixed() { + + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar/baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo/bar/baz"), 0); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo\\bar\\baz"), 0); + +} + +/** + * Generates a dynamically-allocated path having the given number of bytes, not + * counting the null-terminator. The path will contain only UNIX-style path + * separators. The returned path must eventually be freed with a call to + * free(). + * + * @param length + * The number of bytes to include in the generated path, not counting the + * null-terminator. + * + * @return + * A dynamically-allocated path containing the given number of bytes, not + * counting the null-terminator. This path must eventually be freed with a + * call to free(). + */ +static char* generate_path(int length) { + + int i; + char* input = malloc(length + 1); + + /* Fill path with /x/x/x/x/x/x/x/x/x/x/... */ + for (i = 0; i < length; i++) { + input[i] = (i % 2 == 0) ? '/' : 'x'; + } + + /* Add null terminator */ + input[length] = '\0'; + + return input; + +} + +/** + * Test which verifies that paths exceeding the maximum path length are + * rejected. + */ +void test_fs__normalize_long() { + + char* input; + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + /* Exceeds maximum length by a factor of 2 */ + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH*2); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + + /* Exceeds maximum length by one byte */ + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + + /* Exactly maximum length */ + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1); + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + +} + From 591e494dfd42bdc466844ed23631932cf8a0aef9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 13:55:15 -0700 Subject: [PATCH 112/175] GUACAMOLE-637: Add unit tests for RDP filesystem path normalization. --- configure.ac | 5 + src/protocols/rdp/.gitignore | 8 + src/protocols/rdp/Makefile.am | 1 + src/protocols/rdp/tests/Makefile.am | 64 ++++++ src/protocols/rdp/tests/fs/normalize_path.c | 216 ++++++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 src/protocols/rdp/.gitignore create mode 100644 src/protocols/rdp/tests/Makefile.am create mode 100644 src/protocols/rdp/tests/fs/normalize_path.c diff --git a/configure.ac b/configure.ac index 13231239..505f696b 100644 --- a/configure.ac +++ b/configure.ac @@ -151,6 +151,10 @@ AC_SUBST([PULSE_INCLUDE], '-I$(top_srcdir)/src/pulse') AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la') AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh') +# RDP support +AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la') +AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp') + # Terminal emulator AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la') AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)') @@ -1331,6 +1335,7 @@ AC_CONFIG_FILES([Makefile src/pulse/Makefile src/protocols/kubernetes/Makefile src/protocols/rdp/Makefile + src/protocols/rdp/tests/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile src/protocols/vnc/Makefile]) diff --git a/src/protocols/rdp/.gitignore b/src/protocols/rdp/.gitignore new file mode 100644 index 00000000..9f87ecb2 --- /dev/null +++ b/src/protocols/rdp/.gitignore @@ -0,0 +1,8 @@ + +# Auto-generated test runner and binary +_generated_runner.c +test_rdp + +# Autogenerated sources +_generated_keymaps.c + diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 05a7a3ab..cbb6c4f1 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -27,6 +27,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-rdp.la +SUBDIRS = . tests nodist_libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c diff --git a/src/protocols/rdp/tests/Makefile.am b/src/protocols/rdp/tests/Makefile.am new file mode 100644 index 00000000..a803b638 --- /dev/null +++ b/src/protocols/rdp/tests/Makefile.am @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +# +# Unit tests for RDP support +# + +check_PROGRAMS = test_rdp +TESTS = $(check_PROGRAMS) + +test_rdp_SOURCES = \ + fs/normalize_path.c + +test_rdp_CFLAGS = \ + -Werror -Wall -pedantic \ + @LIBGUAC_CLIENT_RDP_INCLUDE@ + +test_rdp_LDADD = \ + @CUNIT_LIBS@ \ + @LIBGUAC_CLIENT_RDP_LTLIB@ + +# +# Autogenerate test runner +# + +GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl +CLEANFILES = _generated_runner.c + +_generated_runner.c: $(test_rdp_SOURCES) + $(AM_V_GEN) $(GEN_RUNNER) $(test_rdp_SOURCES) > $@ + +nodist_test_rdp_SOURCES = \ + _generated_runner.c + +# Use automake's TAP test driver for running any tests +LOG_DRIVER = \ + env AM_TAP_AWK='$(AWK)' \ + $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh + diff --git a/src/protocols/rdp/tests/fs/normalize_path.c b/src/protocols/rdp/tests/fs/normalize_path.c new file mode 100644 index 00000000..0d8f17fa --- /dev/null +++ b/src/protocols/rdp/tests/fs/normalize_path.c @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "rdp_fs.h" + +#include +#include + +/** + * Test which verifies absolute Windows-style paths are correctly normalized to + * absolute paths with Windows separators and no relative components. + */ +void test_fs__normalize_absolute_windows() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\bar\\baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\bar\\..\\baz\\", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\bar\\..\\..\\baz\\a\\..\\b", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz\\b", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\.\\bar\\baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\bar\\..\\..\\..\\..\\..\\..\\baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz", sizeof(normalized)); + +} + +/** + * Test which verifies absolute UNIX-style paths are correctly normalized to + * absolute paths with Windows separators and no relative components. + */ +void test_fs__normalize_absolute_unix() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo/bar/baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo/bar/../baz/", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo/bar/../../baz/a/../b", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz\\b", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo/./bar/baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo/bar/../../../../../../baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz", sizeof(normalized)); + +} + +/** + * Test which verifies absolute paths consisting of mixed Windows and UNIX path + * separators are correctly normalized to absolute paths with Windows + * separators and no relative components. + */ +void test_fs__normalize_absolute_mixed() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo/bar\\baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("/foo\\bar/..\\baz/", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo/bar\\../../baz\\a\\..\\b", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz\\b", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo\\.\\bar/baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\foo\\bar\\baz", sizeof(normalized)); + + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path("\\foo/bar\\../..\\..\\..\\../..\\baz", normalized), 0) + CU_ASSERT_NSTRING_EQUAL(normalized, "\\baz", sizeof(normalized)); + +} + +/** + * Test which verifies relative Windows-style paths are always rejected. + */ +void test_fs__normalize_relative_windows() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(".", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("..", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(".\\foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("..\\foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("foo\\bar\\baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(".\\foo\\bar\\baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("..\\foo\\bar\\baz", normalized), 0) + +} + +/** + * Test which verifies relative UNIX-style paths are always rejected. + */ +void test_fs__normalize_relative_unix() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(".", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("..", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("./foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("../foo", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("foo/bar/baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("./foo/bar/baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("../foo/bar/baz", normalized), 0) + +} + +/** + * Test which verifies relative paths consisting of mixed Windows and UNIX path + * separators are always rejected. + */ +void test_fs__normalize_relative_mixed() { + + char normalized[GUAC_RDP_FS_MAX_PATH]; + + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("foo\\bar/baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(".\\foo/bar/baz", normalized), 0) + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path("../foo\\bar\\baz", normalized), 0) + +} + +/** + * Generates a dynamically-allocated path having the given number of bytes, not + * counting the null-terminator. The path will contain only Windows-style path + * separators. The returned path must eventually be freed with a call to + * free(). + * + * @param length + * The number of bytes to include in the generated path, not counting the + * null-terminator. + * + * @return + * A dynamically-allocated path containing the given number of bytes, not + * counting the null-terminator. This path must eventually be freed with a + * call to free(). + */ +static char* generate_path(int length) { + + int i; + char* input = malloc(length + 1); + + /* Fill path with \x\x\x\x\x\x\x\x\x\x\... */ + for (i = 0; i < length; i++) { + input[i] = (i % 2 == 0) ? '\\' : 'x'; + } + + /* Add null terminator */ + input[length] = '\0'; + + return input; + +} + +/** + * Test which verifies that paths exceeding the maximum path length are + * rejected. + */ +void test_fs__normalize_long() { + + char* input; + char normalized[GUAC_RDP_FS_MAX_PATH]; + + /* Exceeds maximum length by a factor of 2 */ + input = generate_path(GUAC_RDP_FS_MAX_PATH*2); + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + + /* Exceeds maximum length by one byte */ + input = generate_path(GUAC_RDP_FS_MAX_PATH); + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + + /* Exactly maximum length */ + input = generate_path(GUAC_RDP_FS_MAX_PATH - 1); + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + +} + From 159198057993c1f06af3879923bb3de7895a97b0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 16:14:00 -0700 Subject: [PATCH 113/175] GUACAMOLE-637: Simplify SFTP path normalization logic. Correct behavior to match documentation. --- src/common-ssh/sftp.c | 57 +++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/common-ssh/sftp.c b/src/common-ssh/sftp.c index 786ccc8d..b05409b5 100644 --- a/src/common-ssh/sftp.c +++ b/src/common-ssh/sftp.c @@ -36,77 +36,66 @@ int guac_common_ssh_sftp_normalize_path(char* fullpath, const char* path) { - int i; - int path_depth = 0; - char path_component_data[GUAC_COMMON_SSH_SFTP_MAX_PATH]; const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH]; - const char* current_path_component_data = &(path_component_data[0]); - /* If original path is not absolute, normalization fails */ if (path[0] != '\\' && path[0] != '/') return 0; - /* Skip past leading slash */ - path++; + /* Create scratch copy of path excluding leading slash (we will be + * replacing path separators with null terminators and referencing those + * substrings directly as path components) */ + char path_scratch[GUAC_COMMON_SSH_SFTP_MAX_PATH - 1]; + int length = guac_strlcpy(path_scratch, path + 1, + sizeof(path_scratch)); - /* Copy path into component data for parsing */ - int length = guac_strlcpy(path_component_data, path, - sizeof(path_component_data)); - - /* Fail if input path was truncated */ - if (length >= sizeof(path_component_data)) + /* Fail if provided path is too long */ + if (length >= sizeof(path_scratch)) return 0; - /* Find path components within path */ - for (i = 0; i < sizeof(path_component_data); i++) { + /* Locate all path components within path */ + const char* current_path_component = &(path_scratch[0]); + for (int i = 0; i <= length; i++) { /* If current character is a path separator, parse as component */ - char c = path_component_data[i]; + char c = path_scratch[i]; if (c == '/' || c == '\\' || c == '\0') { /* Terminate current component */ - path_component_data[i] = '\0'; + path_scratch[i] = '\0'; /* If component refers to parent, just move up in depth */ - if (strcmp(current_path_component_data, "..") == 0) { + if (strcmp(current_path_component, "..") == 0) { if (path_depth > 0) path_depth--; } /* Otherwise, if component not current directory, add to list */ - else if (strcmp(current_path_component_data, ".") != 0 - && strcmp(current_path_component_data, "") != 0) { + else if (strcmp(current_path_component, ".") != 0 + && strcmp(current_path_component, "") != 0) { /* Fail normalization if path is too deep */ if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH) return 0; - path_components[path_depth++] = current_path_component_data; + path_components[path_depth++] = current_path_component; } - /* If end of string, stop */ - if (c == '\0') - break; - /* Update start of next component */ - current_path_component_data = &(path_component_data[i+1]); + current_path_component = &(path_scratch[i+1]); } /* end if separator */ } /* end for each character */ - /* If no components, the path is simply root */ - if (path_depth == 0) { - strcpy(fullpath, "/"); - return 1; - } + /* Add leading slash for resulting absolute path */ + fullpath[0] = '/'; - /* Convert components back into path */ - guac_strljoin(fullpath, path_components, path_depth, - "/", GUAC_COMMON_SSH_SFTP_MAX_PATH); + /* Append normalized components to path, separated by slashes */ + guac_strljoin(fullpath + 1, path_components, path_depth, + "/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1); return 1; From 986f7f5d6468f1344507c7b82a1df10cd44d2c27 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 16:30:27 -0700 Subject: [PATCH 114/175] GUACAMOLE-637: Use same logic for RDP filesystem path normalization as SFTP. --- src/protocols/rdp/rdp_fs.c | 68 +++++++++++++++----------------------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index 6b9bf044..0e7345f9 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -607,66 +607,55 @@ const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) { int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { - int i; - - char path_component_data[GUAC_RDP_FS_MAX_PATH]; - const char* current_path_component_data = &(path_component_data[0]); - - /* Always include a blank path component at the beginning, such that the - * eventual call to guac_strljoin() will produce an absolute path (leading - * backslash) */ - int path_depth = 1; - const char* path_components[GUAC_RDP_MAX_PATH_DEPTH] = { "" }; + int path_depth = 0; + const char* path_components[GUAC_RDP_MAX_PATH_DEPTH]; /* If original path is not absolute, normalization fails */ if (path[0] != '\\' && path[0] != '/') return 1; - /* Skip past leading slash */ - path++; + /* Create scratch copy of path excluding leading slash (we will be + * replacing path separators with null terminators and referencing those + * substrings directly as path components) */ + char path_scratch[GUAC_RDP_FS_MAX_PATH - 1]; + int length = guac_strlcpy(path_scratch, path + 1, + sizeof(path_scratch)); - /* Copy path into component data for parsing */ - int length = guac_strlcpy(path_component_data, path, - sizeof(path_component_data)); - - /* Fail if input path was truncated */ - if (length >= sizeof(path_component_data)) + /* Fail if provided path is too long */ + if (length >= sizeof(path_scratch)) return 1; - /* Find path components within path */ - for (i = 0; i < sizeof(path_component_data); i++) { + /* Locate all path components within path */ + const char* current_path_component = &(path_scratch[0]); + for (int i = 0; i <= length; i++) { /* If current character is a path separator, parse as component */ - char c = path_component_data[i]; - if (c == '/' || c == '\\' || c == 0) { + char c = path_scratch[i]; + if (c == '/' || c == '\\' || c == '\0') { /* Terminate current component */ - path_component_data[i] = 0; + path_scratch[i] = '\0'; /* If component refers to parent, just move up in depth */ - if (strcmp(current_path_component_data, "..") == 0) { - if (path_depth > 1) + if (strcmp(current_path_component, "..") == 0) { + if (path_depth > 0) path_depth--; } /* Otherwise, if component not current directory, add to list */ - else if (strcmp(current_path_component_data, ".") != 0 - && strcmp(current_path_component_data, "") != 0) { + else if (strcmp(current_path_component, ".") != 0 + && strcmp(current_path_component, "") != 0) { /* Fail normalization if path is too deep */ if (path_depth >= GUAC_RDP_MAX_PATH_DEPTH) return 1; - path_components[path_depth++] = current_path_component_data; + path_components[path_depth++] = current_path_component; } - /* If end of string, stop */ - if (c == 0) - break; - /* Update start of next component */ - current_path_component_data = &(path_component_data[i+1]); + current_path_component = &(path_scratch[i+1]); } /* end if separator */ @@ -676,15 +665,12 @@ int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { } /* end for each character */ - /* If no components, the path is simply root */ - if (path_depth == 0) { - strcpy(abs_path, "\\"); - return 0; - } + /* Add leading slash for resulting absolute path */ + abs_path[0] = '\\'; - /* Convert components back into path */ - guac_strljoin(abs_path, path_components, path_depth, - "\\", GUAC_RDP_FS_MAX_PATH); + /* Append normalized components to path, separated by slashes */ + guac_strljoin(abs_path + 1, path_components, path_depth, + "\\", GUAC_RDP_FS_MAX_PATH - 1); return 0; From 6e2be38ae2bc79623dc7412a3547788d1ea1f31f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 16:36:16 -0700 Subject: [PATCH 115/175] GUACAMOLE-637: Add path depth limits to generated paths in unit tests. --- src/common-ssh/tests/sftp/normalize_path.c | 21 +++++++++++++++------ src/protocols/rdp/tests/fs/normalize_path.c | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/common-ssh/tests/sftp/normalize_path.c b/src/common-ssh/tests/sftp/normalize_path.c index 23975252..79f4d426 100644 --- a/src/common-ssh/tests/sftp/normalize_path.c +++ b/src/common-ssh/tests/sftp/normalize_path.c @@ -166,19 +166,28 @@ void test_fs__normalize_relative_mixed() { * The number of bytes to include in the generated path, not counting the * null-terminator. * + * @param max_depth + * The maximum number of path components to include within the generated + * path. + * * @return * A dynamically-allocated path containing the given number of bytes, not * counting the null-terminator. This path must eventually be freed with a * call to free(). */ -static char* generate_path(int length) { +static char* generate_path(int length, int max_depth) { int i; char* input = malloc(length + 1); - /* Fill path with /x/x/x/x/x/x/x/x/x/x/... */ + /* Fill path with /x/x/x/x/x/x/x/x/x/x/.../xxxxxxxxx... */ for (i = 0; i < length; i++) { - input[i] = (i % 2 == 0) ? '/' : 'x'; + if (max_depth > 0 && i % 2 == 0) { + input[i] = '/'; + max_depth--; + } + else + input[i] = 'x'; } /* Add null terminator */ @@ -198,17 +207,17 @@ void test_fs__normalize_long() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; /* Exceeds maximum length by a factor of 2 */ - input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH*2); + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH*2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); free(input); /* Exceeds maximum length by one byte */ - input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH); + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); free(input); /* Exactly maximum length */ - input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1); + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); free(input); diff --git a/src/protocols/rdp/tests/fs/normalize_path.c b/src/protocols/rdp/tests/fs/normalize_path.c index 0d8f17fa..02fbc6a6 100644 --- a/src/protocols/rdp/tests/fs/normalize_path.c +++ b/src/protocols/rdp/tests/fs/normalize_path.c @@ -166,19 +166,28 @@ void test_fs__normalize_relative_mixed() { * The number of bytes to include in the generated path, not counting the * null-terminator. * + * @param max_depth + * The maximum number of path components to include within the generated + * path. + * * @return * A dynamically-allocated path containing the given number of bytes, not * counting the null-terminator. This path must eventually be freed with a * call to free(). */ -static char* generate_path(int length) { +static char* generate_path(int length, int max_depth) { int i; char* input = malloc(length + 1); - /* Fill path with \x\x\x\x\x\x\x\x\x\x\... */ + /* Fill path with \x\x\x\x\x\x\x\x\x\x\...\xxxxxxxxx... */ for (i = 0; i < length; i++) { - input[i] = (i % 2 == 0) ? '\\' : 'x'; + if (max_depth > 0 && i % 2 == 0) { + input[i] = '\\'; + max_depth--; + } + else + input[i] = 'x'; } /* Add null terminator */ @@ -198,17 +207,17 @@ void test_fs__normalize_long() { char normalized[GUAC_RDP_FS_MAX_PATH]; /* Exceeds maximum length by a factor of 2 */ - input = generate_path(GUAC_RDP_FS_MAX_PATH*2); + input = generate_path(GUAC_RDP_FS_MAX_PATH*2, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); free(input); /* Exceeds maximum length by one byte */ - input = generate_path(GUAC_RDP_FS_MAX_PATH); + input = generate_path(GUAC_RDP_FS_MAX_PATH, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); free(input); /* Exactly maximum length */ - input = generate_path(GUAC_RDP_FS_MAX_PATH - 1); + input = generate_path(GUAC_RDP_FS_MAX_PATH - 1, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); free(input); From cda7bca126f8d5e9b7b200d34fe8a648fe752d51 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 16:51:33 -0700 Subject: [PATCH 116/175] GUACAMOLE-637: Add RDP filesystem and SFTP unit tests for path depth. --- src/common-ssh/tests/sftp/normalize_path.c | 42 ++++++++++++++++++++- src/protocols/rdp/tests/fs/normalize_path.c | 35 ++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/common-ssh/tests/sftp/normalize_path.c b/src/common-ssh/tests/sftp/normalize_path.c index 79f4d426..a33dbad6 100644 --- a/src/common-ssh/tests/sftp/normalize_path.c +++ b/src/common-ssh/tests/sftp/normalize_path.c @@ -164,7 +164,8 @@ void test_fs__normalize_relative_mixed() { * * @param length * The number of bytes to include in the generated path, not counting the - * null-terminator. + * null-terminator. If -1, the length of the path will be automatically + * determined from the provided max_depth. * * @param max_depth * The maximum number of path components to include within the generated @@ -177,6 +178,10 @@ void test_fs__normalize_relative_mixed() { */ static char* generate_path(int length, int max_depth) { + /* If no length given, calculate space required from max_depth */ + if (length == -1) + length = max_depth * 2; + int i; char* input = malloc(length + 1); @@ -207,7 +212,7 @@ void test_fs__normalize_long() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; /* Exceeds maximum length by a factor of 2 */ - input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH*2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); + input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH * 2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); free(input); @@ -223,3 +228,36 @@ void test_fs__normalize_long() { } +/** + * Test which verifies that paths exceeding the maximum path depth are + * rejected. + */ +void test_fs__normalize_deep() { + + char* input; + char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; + + /* Exceeds maximum depth by a factor of 2 */ + input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH * 2); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + + /* Exceeds maximum depth by one component */ + input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH + 1); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + + /* Exactly maximum depth (should still be rejected as SFTP depth limits are + * set such that a path with the maximum depth will exceed the maximum + * length) */ + input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH); + CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + + /* Less than maximum depth */ + input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH - 1); + CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0); + free(input); + +} + diff --git a/src/protocols/rdp/tests/fs/normalize_path.c b/src/protocols/rdp/tests/fs/normalize_path.c index 02fbc6a6..ccf23e01 100644 --- a/src/protocols/rdp/tests/fs/normalize_path.c +++ b/src/protocols/rdp/tests/fs/normalize_path.c @@ -164,7 +164,8 @@ void test_fs__normalize_relative_mixed() { * * @param length * The number of bytes to include in the generated path, not counting the - * null-terminator. + * null-terminator. If -1, the length of the path will be automatically + * determined from the provided max_depth. * * @param max_depth * The maximum number of path components to include within the generated @@ -177,6 +178,10 @@ void test_fs__normalize_relative_mixed() { */ static char* generate_path(int length, int max_depth) { + /* If no length given, calculate space required from max_depth */ + if (length == -1) + length = max_depth * 2; + int i; char* input = malloc(length + 1); @@ -207,7 +212,7 @@ void test_fs__normalize_long() { char normalized[GUAC_RDP_FS_MAX_PATH]; /* Exceeds maximum length by a factor of 2 */ - input = generate_path(GUAC_RDP_FS_MAX_PATH*2, GUAC_RDP_MAX_PATH_DEPTH); + input = generate_path(GUAC_RDP_FS_MAX_PATH * 2, GUAC_RDP_MAX_PATH_DEPTH); CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); free(input); @@ -223,3 +228,29 @@ void test_fs__normalize_long() { } +/** + * Test which verifies that paths exceeding the maximum path depth are + * rejected. + */ +void test_fs__normalize_deep() { + + char* input; + char normalized[GUAC_RDP_FS_MAX_PATH]; + + /* Exceeds maximum depth by a factor of 2 */ + input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH * 2); + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + + /* Exceeds maximum depth by one component */ + input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH + 1); + CU_ASSERT_NOT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + + /* Exactly maximum depth */ + input = generate_path(-1, GUAC_RDP_MAX_PATH_DEPTH); + CU_ASSERT_EQUAL(guac_rdp_fs_normalize_path(input, normalized), 0); + free(input); + +} + From f8ec709e334847c96cdc649a2ee43e5a42a3deb2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Apr 2019 16:51:53 -0700 Subject: [PATCH 117/175] GUACAMOLE-637: Correct naming of SFTP unit tests. --- src/common-ssh/tests/sftp/normalize_path.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common-ssh/tests/sftp/normalize_path.c b/src/common-ssh/tests/sftp/normalize_path.c index a33dbad6..151b1835 100644 --- a/src/common-ssh/tests/sftp/normalize_path.c +++ b/src/common-ssh/tests/sftp/normalize_path.c @@ -26,7 +26,7 @@ * Test which verifies absolute Windows-style paths are correctly normalized to * absolute paths with UNIX separators and no relative components. */ -void test_fs__normalize_absolute_windows() { +void test_sftp__normalize_absolute_windows() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -54,7 +54,7 @@ void test_fs__normalize_absolute_windows() { * Test which verifies absolute UNIX-style paths are correctly normalized to * absolute paths with UNIX separators and no relative components. */ -void test_fs__normalize_absolute_unix() { +void test_sftp__normalize_absolute_unix() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -83,7 +83,7 @@ void test_fs__normalize_absolute_unix() { * separators are correctly normalized to absolute paths with UNIX separators * and no relative components. */ -void test_fs__normalize_absolute_mixed() { +void test_sftp__normalize_absolute_mixed() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -107,7 +107,7 @@ void test_fs__normalize_absolute_mixed() { /** * Test which verifies relative Windows-style paths are always rejected. */ -void test_fs__normalize_relative_windows() { +void test_sftp__normalize_relative_windows() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -126,7 +126,7 @@ void test_fs__normalize_relative_windows() { /** * Test which verifies relative UNIX-style paths are always rejected. */ -void test_fs__normalize_relative_unix() { +void test_sftp__normalize_relative_unix() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -146,7 +146,7 @@ void test_fs__normalize_relative_unix() { * Test which verifies relative paths consisting of mixed Windows and UNIX path * separators are always rejected. */ -void test_fs__normalize_relative_mixed() { +void test_sftp__normalize_relative_mixed() { char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -206,7 +206,7 @@ static char* generate_path(int length, int max_depth) { * Test which verifies that paths exceeding the maximum path length are * rejected. */ -void test_fs__normalize_long() { +void test_sftp__normalize_long() { char* input; char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; @@ -232,7 +232,7 @@ void test_fs__normalize_long() { * Test which verifies that paths exceeding the maximum path depth are * rejected. */ -void test_fs__normalize_deep() { +void test_sftp__normalize_deep() { char* input; char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH]; From 61070cb3676927e95870681d6158ce0e564f6cf4 Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 8 Apr 2019 12:28:46 +0200 Subject: [PATCH 118/175] GUACAMOLE-772: Switched runtime image to debian slim --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f5f7a425..094842cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ RUN ${PREFIX_DIR}/bin/list-dependencies.sh \ > ${PREFIX_DIR}/DEPENDENCIES # Use same Debian as the base for the runtime image -FROM debian:${DEBIAN_VERSION} +FROM debian:${DEBIAN_VERSION}-slim # Base directory for installed build artifacts. # Due to limitations of the Docker image build process, this value is From 2db7ffbaab88d63c6fbefd9c4a3419d8e90a9cb4 Mon Sep 17 00:00:00 2001 From: m-khan-glyptodon Date: Mon, 8 Apr 2019 15:00:59 -0700 Subject: [PATCH 119/175] GUACAMOLE-764: Updated variable type for offsets to uint64_t to increase rdp read to a 64-bit addressable space --- src/protocols/rdp/rdp_fs.c | 2 +- src/protocols/rdp/rdp_fs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index ab48cc2f..d32da681 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -402,7 +402,7 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, } -int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset, +int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset, void* buffer, int length) { int bytes_read; diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/rdp_fs.h index 9168efb1..8148725e 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/rdp_fs.h @@ -487,7 +487,7 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, * error occurs. All error codes are negative values and correspond to * GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ -int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, int offset, +int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset, void* buffer, int length); /** From 871f31353be9fcf127d2a78893e5456dbcb17ce4 Mon Sep 17 00:00:00 2001 From: m-khan-glyptodon Date: Mon, 8 Apr 2019 15:27:58 -0700 Subject: [PATCH 120/175] GUACAMOLE-764: Updated variable type for offsets to uint64_t to increase rdp write to a 64-bit addressable space --- src/protocols/rdp/rdp_fs.c | 2 +- src/protocols/rdp/rdp_fs.h | 2 +- src/protocols/rdp/rdp_stream.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/protocols/rdp/rdp_fs.c b/src/protocols/rdp/rdp_fs.c index d32da681..911d663f 100644 --- a/src/protocols/rdp/rdp_fs.c +++ b/src/protocols/rdp/rdp_fs.c @@ -426,7 +426,7 @@ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset, } -int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset, +int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset, void* buffer, int length) { int bytes_written; diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/rdp_fs.h index 8148725e..8a754fbb 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/rdp_fs.h @@ -515,7 +515,7 @@ int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset, * occurs. All error codes are negative values and correspond to * GUAC_RDP_FS constants, such as GUAC_RDP_FS_ENOENT. */ -int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, int offset, +int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset, void* buffer, int length); /** diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h index c9c3838e..deec4f9a 100644 --- a/src/protocols/rdp/rdp_stream.h +++ b/src/protocols/rdp/rdp_stream.h @@ -57,7 +57,7 @@ typedef struct guac_rdp_upload_status { * The overall offset within the file that the next write should * occur at. */ - int offset; + uint64_t offset; /** * The ID of the file being written to. From 0c257820361aadb8c24c35c8e135ba31778efedc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 9 Apr 2019 23:36:03 -0700 Subject: [PATCH 121/175] GUACAMOLE-637: Add missing libguac include path to RDP and common-ssh tests. --- src/common-ssh/tests/Makefile.am | 3 ++- src/protocols/rdp/tests/Makefile.am | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common-ssh/tests/Makefile.am b/src/common-ssh/tests/Makefile.am index b26d5bbf..12396991 100644 --- a/src/common-ssh/tests/Makefile.am +++ b/src/common-ssh/tests/Makefile.am @@ -39,7 +39,8 @@ test_common_ssh_SOURCES = \ test_common_ssh_CFLAGS = \ -Werror -Wall -pedantic \ @COMMON_INCLUDE@ \ - @COMMON_SSH_INCLUDE@ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ test_common_ssh_LDADD = \ @CUNIT_LIBS@ \ diff --git a/src/protocols/rdp/tests/Makefile.am b/src/protocols/rdp/tests/Makefile.am index a803b638..3f57bbf3 100644 --- a/src/protocols/rdp/tests/Makefile.am +++ b/src/protocols/rdp/tests/Makefile.am @@ -38,7 +38,8 @@ test_rdp_SOURCES = \ test_rdp_CFLAGS = \ -Werror -Wall -pedantic \ - @LIBGUAC_CLIENT_RDP_INCLUDE@ + @LIBGUAC_CLIENT_RDP_INCLUDE@ \ + @LIBGUAC_INCLUDE@ test_rdp_LDADD = \ @CUNIT_LIBS@ \ From f70aa4939fc8c69126037317fcefa05606311b19 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 24 Mar 2019 21:36:44 -0400 Subject: [PATCH 122/175] GUACAMOLE-422: Add client timezone to handshake. --- src/libguac/guacamole/user.h | 7 +++++++ src/libguac/user-handshake.c | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 792c7421..75accdb9 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -88,6 +88,13 @@ struct guac_user_info { * stated resolution of the display size request is recommended. */ int optimal_resolution; + + /** + * The timezone of the remote system. If the client does not provide + * a specific timezone then this will be NULL. The format of the timezone + * is the standard tzdata naming convention. + */ + const char** timezone; }; diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 13bea51f..87354054 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -384,6 +384,31 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { /* Store image mimetypes */ char** image_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); user->info.image_mimetypes = (const char**) image_mimetypes; + + /* Get client timezone */ + if (guac_parser_expect(parser, socket, usec_timeout, "timezone")) { + + /* Log error */ + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error reading \"timezone\""); + + guac_parser_free(parser); + return 1; + + } + + /* Check number of timezone arguments */ + if (parser->argc < 1) { + guac_user_log(user, GUAC_LOG_ERROR, "Received \"timezone\" instruction " + "lacked required arguments."); + guac_parser_free(parser); + return 1; + } + + /* Store timezone */ + char** timezone = parser->argv[0]; + user->info.timezone = (const char**) timezone; /* Get args from connect instruction */ if (guac_parser_expect(parser, socket, usec_timeout, "connect")) { From 6fae0b4b23b70a98fdb91826fc20aaeda0378e4f Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 24 Mar 2019 21:55:45 -0400 Subject: [PATCH 123/175] GUACAMOLE-422: Use timezone from handshake when parameter does not exist. --- src/libguac/guacamole/user.h | 2 +- src/libguac/user-handshake.c | 11 ++++++++--- src/protocols/rdp/rdp_settings.c | 4 ++-- src/protocols/ssh/settings.c | 4 ++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 75accdb9..adc4c4e9 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -94,7 +94,7 @@ struct guac_user_info { * a specific timezone then this will be NULL. The format of the timezone * is the standard tzdata naming convention. */ - const char** timezone; + const char* timezone; }; diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 87354054..b0a9b2f3 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -406,9 +406,10 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { return 1; } - /* Store timezone */ - char** timezone = parser->argv[0]; - user->info.timezone = (const char**) timezone; + /* Store timezone, if present */ + char* timezone = parser->argv[0]; + if (timezone != NULL && !strcmp(timezone, "")) + user->info.timezone = (const char*) timezone; /* Get args from connect instruction */ if (guac_parser_expect(parser, socket, usec_timeout, "connect")) { @@ -452,6 +453,10 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_free_mimetypes(audio_mimetypes); guac_free_mimetypes(video_mimetypes); guac_free_mimetypes(image_mimetypes); + + /* Free timezone */ + if (timezone != NULL) + free(timezone); guac_parser_free(parser); diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index d322a480..e5aa85dd 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -851,10 +851,10 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, if (settings->server_layout == NULL) settings->server_layout = guac_rdp_keymap_find(GUAC_DEFAULT_KEYMAP); - /* Timezone if provied by client */ + /* Timezone if provided by client, or use handshake version */ settings->timezone = guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv, - IDX_TIMEZONE, NULL); + IDX_TIMEZONE, user->info.timezone); #ifdef ENABLE_COMMON_SSH /* SFTP enable/disable */ diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 962524ce..74706e0d 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -425,6 +425,10 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, settings->timezone = guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_TIMEZONE, NULL); + + /* If timezone not explicitly set, try to pull from tunnel */ + if (settings->timezone == NULL) + settings->timezone = user->info.timezone; /* Parsing was successful */ return settings; From 5caa8a25f7e3fb228356c51fe5cbca181c185134 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 25 Mar 2019 17:23:58 -0400 Subject: [PATCH 124/175] GUACAMOLE-422: SSH parameter should use handshake for default. --- src/protocols/ssh/settings.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index 74706e0d..a0af9f9b 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -421,14 +421,10 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, IDX_LOCALE, NULL); - /* Read the client timezone. */ + /* Read the timezone parameter, or use client handshake. */ settings->timezone = guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv, - IDX_TIMEZONE, NULL); - - /* If timezone not explicitly set, try to pull from tunnel */ - if (settings->timezone == NULL) - settings->timezone = user->info.timezone; + IDX_TIMEZONE, user->info.timezone); /* Parsing was successful */ return settings; From 5480b288e8ee08d6b2572dbbbb40883600d384cf Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 25 Mar 2019 17:26:37 -0400 Subject: [PATCH 125/175] GUACAMOLE-422: Remove NULL check for parser argv. --- src/libguac/user-handshake.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index b0a9b2f3..e5143927 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -408,7 +408,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { /* Store timezone, if present */ char* timezone = parser->argv[0]; - if (timezone != NULL && !strcmp(timezone, "")) + if (!strcmp(timezone, "")) user->info.timezone = (const char*) timezone; /* Get args from connect instruction */ From 0ee47e01869d5fc6d25c059f2bd23fa5420f17ce Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Thu, 28 Mar 2019 20:09:32 -0400 Subject: [PATCH 126/175] GUACAMOLE-422: Change handshake to ignore order of opcodes. --- src/libguac/guacamole/user.h | 73 +++++++++++ src/libguac/user-handshake.c | 246 ++++++++++++++++++----------------- 2 files changed, 198 insertions(+), 121 deletions(-) diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index adc4c4e9..137553a7 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -505,6 +505,79 @@ struct guac_user { }; +/** + * Handler for Guacamole protocol opcode specific to the handshake that + * happens between client and server at the beginning of the connection. The + * handler will be invoked when the matching opcode is received during the + * handshake process. + * + * @param user + * The user that initiated the handshake. + * + * @param parser + * The parser allocated for parsing the data provided by the client. + * + * @param timeout + * The timeout, in microseconds, for parsing the value. + * + * @return + * Zero if the handshake instruction is successfully parsed; otherwise + * false. + */ +typedef int __guac_handshake_handler(guac_user* user, int argc, char** argv); + +/** + * Structure that maps opcodes received during the handshake phase of the + * connection to callback functions used when those opcodes are received. + */ +typedef struct __guac_handshake_mapping { + + /** + * The instruction opcode which maps to the handler. + */ + char* opcode; + + /** + * The handler function used when specified opcode is received. + */ + __guac_handshake_handler* handler; + +} __guac_handshake_mapping; + +/** + * Internal handler function that is called when the size instruction is + * received during the handshake process. + */ +__guac_handshake_handler __guac_handshake_size_handler; + +/** + * Internal handler function that is called when the audio instruction is + * received during the handshake process, specifying the audio mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_audio_handler; + +/** + * Internal handler function that is called when the video instruction is + * received during the handshake process, specifying the video mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_video_handler; + +/** + * Internal handler function that is called when the image instruction is + * received during the handshake process, specifying the image mimetypes + * available to the client. + */ +__guac_handshake_handler __guac_handshake_image_handler; + +/** + * Internal handler function that is called when the timezone instruction is + * received during the handshake process, specifying the timezone of the + * client. + */ +__guac_handshake_handler __guac_handshake_timezone_handler; + /** * Allocates a new, blank user, not associated with any specific client or * socket. diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index e5143927..a9cf003b 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -171,6 +171,80 @@ static void guac_free_mimetypes(char** mimetypes) { } +/* Guacamole handshake handler functions. */ + +int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { + + /* Validate size of instruction. */ + if (argc < 2) { + guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " + "instruction lacked required arguments."); + return 1; + } + + /* Parse optimal screen dimensions from size instruction */ + user->info.optimal_width = atoi(argv[0]); + user->info.optimal_height = atoi(argv[1]); + + /* If DPI given, set the user resolution */ + if (argc >= 3) + user->info.optimal_resolution = atoi(argv[2]); + + /* Otherwise, use a safe default for rough backwards compatibility */ + else + user->info.optimal_resolution = 96; + + return 0; + +} + +int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { + + /* Store audio mimetypes */ + user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { + + /* Store video mimetypes */ + user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { + + /* Store image mimetypes */ + user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { + + /* Store timezone, if present */ + if (argc > 0 && strcmp(argv[0], "")) + user->info.timezone = (const char*) strdup(argv[0]); + + return 0; + +} + +/* Guacamole handshake handler mappings. */ +__guac_handshake_mapping __guac_handshake_map[] = { + {"size", __guac_handshake_size_handler}, + {"audio", __guac_handshake_audio_handler}, + {"video", __guac_handshake_video_handler}, + {"image", __guac_handshake_image_handler}, + {"timezone", __guac_handshake_timezone_handler}, + {NULL, NULL} +}; + /** * The thread which handles all user input, calling event handlers for received * instructions. @@ -305,122 +379,53 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_parser* parser = guac_parser_alloc(); - /* Get optimal screen size */ - if (guac_parser_expect(parser, socket, usec_timeout, "size")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"size\""); - - guac_parser_free(parser); - return 1; - } - - /* Validate content of size instruction */ - if (parser->argc < 2) { - guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " - "instruction lacked required arguments."); - guac_parser_free(parser); - return 1; - } - - /* Parse optimal screen dimensions from size instruction */ - user->info.optimal_width = atoi(parser->argv[0]); - user->info.optimal_height = atoi(parser->argv[1]); - - /* If DPI given, set the user resolution */ - if (parser->argc >= 3) - user->info.optimal_resolution = atoi(parser->argv[2]); - - /* Otherwise, use a safe default for rough backwards compatibility */ - else - user->info.optimal_resolution = 96; - - /* Get supported audio formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "audio")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"audio\""); - - guac_parser_free(parser); - return 1; - } - - /* Store audio mimetypes */ - char** audio_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.audio_mimetypes = (const char**) audio_mimetypes; - - /* Get supported video formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "video")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"video\""); - - guac_parser_free(parser); - return 1; - } - - /* Store video mimetypes */ - char** video_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.video_mimetypes = (const char**) video_mimetypes; - - /* Get supported image formats */ - if (guac_parser_expect(parser, socket, usec_timeout, "image")) { - - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"image\""); - - guac_parser_free(parser); - return 1; - } - - /* Store image mimetypes */ - char** image_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc); - user->info.image_mimetypes = (const char**) image_mimetypes; - - /* Get client timezone */ - if (guac_parser_expect(parser, socket, usec_timeout, "timezone")) { + /* Handle each of the opcodes. */ + while (1) { + if (guac_parser_read(parser, socket, usec_timeout)) { + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error while reading opcode instruction."); + guac_parser_free(parser); + return 1; + } - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"timezone\""); + /* If we receive the connect opcode, we're done. */ + if (strcmp(parser->opcode, "connect") == 0) + break; - guac_parser_free(parser); - return 1; - - } - - /* Check number of timezone arguments */ - if (parser->argc < 1) { - guac_user_log(user, GUAC_LOG_ERROR, "Received \"timezone\" instruction " - "lacked required arguments."); - guac_parser_free(parser); - return 1; - } - - /* Store timezone, if present */ - char* timezone = parser->argv[0]; - if (!strcmp(timezone, "")) - user->info.timezone = (const char*) timezone; + /* Loop available opcodes and run handler if/when match found. */ + __guac_handshake_mapping* current = __guac_handshake_map; + while (current->opcode != NULL) { + + /* Check if loop opcode matches parsed opcode. */ + if (strcmp(parser->opcode, current->opcode) == 0) { + + /* If calling the handler fails, log it and return. */ + if (current->handler(user, parser->argc, parser->argv)) { + + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error handling handling opcode during handshake."); + guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", + current->opcode); - /* Get args from connect instruction */ - if (guac_parser_expect(parser, socket, usec_timeout, "connect")) { + guac_parser_free(parser); + return 1; + } + + /* If calling the handler has succeeded, log it and break. */ + else { + guac_user_log(user, GUAC_LOG_DEBUG, + "Successfully processed instruction: \"%s\"", + current->opcode); + break; + } - /* Log error */ - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error reading \"connect\""); - - guac_parser_free(parser); - return 1; + } + + /* Move to next opcode. */ + current++; + } } /* Acknowledge connection availability */ @@ -448,16 +453,15 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { "users remain)", user->user_id, client->connected_users); } - - /* Free mimetype lists */ - guac_free_mimetypes(audio_mimetypes); - guac_free_mimetypes(video_mimetypes); - guac_free_mimetypes(image_mimetypes); - /* Free timezone */ - if (timezone != NULL) - free(timezone); - + /* Free mimetype character arrays. */ + guac_free_mimetypes((char **) user->info.audio_mimetypes); + guac_free_mimetypes((char **) user->info.image_mimetypes); + guac_free_mimetypes((char **) user->info.video_mimetypes); + + /* Free timezone info. */ + free((char *) user->info.timezone); + guac_parser_free(parser); /* Successful disconnect */ From 2b68925ec9cd8168950a03dc33c79a643c96c9fc Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 30 Mar 2019 13:43:01 -0400 Subject: [PATCH 127/175] GUACAMOLE-422: Add protocol version as initial item passed back in args. --- src/libguac/user-handshake.c | 10 ++++++++-- src/protocols/kubernetes/settings.c | 13 +++++++++++++ src/protocols/rdp/rdp_settings.c | 13 +++++++++++++ src/protocols/ssh/settings.c | 13 +++++++++++++ src/protocols/telnet/settings.c | 13 +++++++++++++ src/protocols/vnc/settings.c | 13 +++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index a9cf003b..e9ccbacd 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -199,7 +199,7 @@ int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { } int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { - + /* Store audio mimetypes */ user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); @@ -208,7 +208,7 @@ int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { } int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { - + /* Store video mimetypes */ user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); @@ -227,6 +227,9 @@ int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { + /* Free any past value */ + free((char *) user->info.timezone); + /* Store timezone, if present */ if (argc > 0 && strcmp(argv[0], "")) user->info.timezone = (const char*) strdup(argv[0]); @@ -393,6 +396,9 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { if (strcmp(parser->opcode, "connect") == 0) break; + guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", + parser->opcode); + /* Loop available opcodes and run handler if/when match found. */ __guac_handshake_mapping* current = __guac_handshake_map; while (current->opcode != NULL) { diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 4f00a445..6e8ddd41 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -25,6 +25,14 @@ /* Client plugin arguments */ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { + /** + * This first argument defines the protocol version in use so that the + * client knows how to handle talking to different versions of guacd. + * If this is omitted the client may choose not to enable certain + * features. + */ + "VERSION_1_1_0", + "hostname", "port", "namespace", @@ -55,6 +63,11 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { enum KUBERNETES_ARGS_IDX { + /** + * The protocol version provided to the client. + */ + IDX_PROTOCOL_VERSION, + /** * The hostname to connect to. Required. */ diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index e5aa85dd..9edcb746 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -42,6 +42,14 @@ /* Client plugin arguments */ const char* GUAC_RDP_CLIENT_ARGS[] = { + /** + * This first argument defines the protocol version in use so that the + * client knows how to handle talking to different versions of guacd. + * If this is omitted the client may choose not to enable certain + * features. + */ + "VERSION_1_1_0", + "hostname", "port", "domain", @@ -124,6 +132,11 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { enum RDP_ARGS_IDX { + /** + * The protocol version sent to the client. + */ + IDX_PROTOCOL_VERSION, + /** * The hostname to connect to. */ diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index a0af9f9b..a404e8ca 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -30,6 +30,14 @@ /* Client plugin arguments */ const char* GUAC_SSH_CLIENT_ARGS[] = { + /** + * This first argument defines the protocol version in use so that the + * client knows how to handle talking to different versions of guacd. + * If this is omitted the client may choose not to enable certain + * features. + */ + "VERSION_1_1_0", + "hostname", "host-key", "port", @@ -66,6 +74,11 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { }; enum SSH_ARGS_IDX { + + /** + * The protocol version + */ + IDX_PROTOCOL_VERSION, /** * The hostname to connect to. Required. diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 890d5fec..4d2d4dd1 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -31,6 +31,14 @@ /* Client plugin arguments */ const char* GUAC_TELNET_CLIENT_ARGS[] = { + /** + * This first argument defines the protocol version in use so that the + * client knows how to handle talking to different versions of guacd. + * If this is omitted the client may choose not to enable certain + * features. + */ + "VERSION_1_1_0", + "hostname", "port", "username", @@ -60,6 +68,11 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { enum TELNET_ARGS_IDX { + /** + * The protocol version provided by the client. + */ + IDX_PROTOCOL_VERSION, + /** * The hostname to connect to. Required. */ diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 8f65cfb2..941a2757 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -31,6 +31,14 @@ /* Client plugin arguments */ const char* GUAC_VNC_CLIENT_ARGS[] = { + /** + * This first argument defines the protocol version in use so that the + * client knows how to handle talking to different versions of guacd. + * If this is omitted the client may choose not to enable certain + * features. + */ + "VERSION_1_1_0", + "hostname", "port", "read-only", @@ -83,6 +91,11 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { enum VNC_ARGS_IDX { + /** + * The protocol version provided to the client. + */ + IDX_PROTOCOL_VERSION, + /** * The hostname of the VNC server (or repeater) to connect to. */ From 2f57564f5d2ed7e8a41179000178bcf6ec4e5487 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 2 Apr 2019 21:38:28 -0400 Subject: [PATCH 128/175] GUACAMOLE-422: Remove duplicate code and migrate handshake to user handlers. --- src/libguac/guacamole/user.h | 99 ------------------- src/libguac/user-handlers.c | 131 ++++++++++++++++++++++++++ src/libguac/user-handlers.h | 101 ++++++++++++++++++++ src/libguac/user-handshake.c | 178 +++-------------------------------- src/libguac/user.c | 18 ---- 5 files changed, 245 insertions(+), 282 deletions(-) diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 137553a7..67ca7c88 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -505,79 +505,6 @@ struct guac_user { }; -/** - * Handler for Guacamole protocol opcode specific to the handshake that - * happens between client and server at the beginning of the connection. The - * handler will be invoked when the matching opcode is received during the - * handshake process. - * - * @param user - * The user that initiated the handshake. - * - * @param parser - * The parser allocated for parsing the data provided by the client. - * - * @param timeout - * The timeout, in microseconds, for parsing the value. - * - * @return - * Zero if the handshake instruction is successfully parsed; otherwise - * false. - */ -typedef int __guac_handshake_handler(guac_user* user, int argc, char** argv); - -/** - * Structure that maps opcodes received during the handshake phase of the - * connection to callback functions used when those opcodes are received. - */ -typedef struct __guac_handshake_mapping { - - /** - * The instruction opcode which maps to the handler. - */ - char* opcode; - - /** - * The handler function used when specified opcode is received. - */ - __guac_handshake_handler* handler; - -} __guac_handshake_mapping; - -/** - * Internal handler function that is called when the size instruction is - * received during the handshake process. - */ -__guac_handshake_handler __guac_handshake_size_handler; - -/** - * Internal handler function that is called when the audio instruction is - * received during the handshake process, specifying the audio mimetypes - * available to the client. - */ -__guac_handshake_handler __guac_handshake_audio_handler; - -/** - * Internal handler function that is called when the video instruction is - * received during the handshake process, specifying the video mimetypes - * available to the client. - */ -__guac_handshake_handler __guac_handshake_video_handler; - -/** - * Internal handler function that is called when the image instruction is - * received during the handshake process, specifying the image mimetypes - * available to the client. - */ -__guac_handshake_handler __guac_handshake_image_handler; - -/** - * Internal handler function that is called when the timezone instruction is - * received during the handshake process, specifying the timezone of the - * client. - */ -__guac_handshake_handler __guac_handshake_timezone_handler; - /** * Allocates a new, blank user, not associated with any specific client or * socket. @@ -617,32 +544,6 @@ void guac_user_free(guac_user* user); */ int guac_user_handle_connection(guac_user* user, int usec_timeout); -/** - * Call the appropriate handler defined by the given user for the given - * instruction. A comparison is made between the instruction opcode and the - * initial handler lookup table defined in user-handlers.c. The intial handlers - * will in turn call the user's handler (if defined). - * - * @param user - * The user whose handlers should be called. - * - * @param opcode - * The opcode of the instruction to pass to the user via the appropriate - * handler. - * - * @param argc - * The number of arguments which are part of the instruction. - * - * @param argv - * An array of all arguments which are part of the instruction. - * - * @return - * Non-negative if the instruction was handled successfully, or negative - * if an error occurred. - */ -int guac_user_handle_instruction(guac_user* user, const char* opcode, - int argc, char** argv); - /** * Allocates a new stream. An arbitrary index is automatically assigned * if no previously-allocated stream is available for use. diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index 6be20e2e..737e6f30 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -31,6 +31,7 @@ #include #include #include +#include /* Guacamole instruction handler map */ @@ -53,6 +54,17 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = { {NULL, NULL} }; +/* Guacamole handshake handler map */ + +__guac_instruction_handler_mapping __guac_handshake_handler_map[] = { + {"size", __guac_handshake_size_handler}, + {"audio", __guac_handshake_audio_handler}, + {"video", __guac_handshake_video_handler}, + {"image", __guac_handshake_image_handler}, + {"timezone", __guac_handshake_timezone_handler}, + {NULL, NULL} +}; + /** * Parses a 64-bit integer from the given string. It is assumed that the string * will contain only decimal digits, with an optional leading minus sign. @@ -581,3 +593,122 @@ int __guac_handle_disconnect(guac_user* user, int argc, char** argv) { return 0; } +/* Guacamole handshake handler functions. */ + +int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { + + /* Validate size of instruction. */ + if (argc < 2) { + guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " + "instruction lacked required arguments."); + return 1; + } + + /* Parse optimal screen dimensions from size instruction */ + user->info.optimal_width = atoi(argv[0]); + user->info.optimal_height = atoi(argv[1]); + + /* If DPI given, set the user resolution */ + if (argc >= 3) + user->info.optimal_resolution = atoi(argv[2]); + + /* Otherwise, use a safe default for rough backwards compatibility */ + else + user->info.optimal_resolution = 96; + + return 0; + +} + +int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { + + /* Store audio mimetypes */ + user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { + + /* Store video mimetypes */ + user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { + + /* Store image mimetypes */ + user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); + + return 0; + +} + +int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { + + /* Free any past value */ + free((char *) user->info.timezone); + + /* Store timezone, if present */ + if (argc > 0 && strcmp(argv[0], "")) + user->info.timezone = (const char*) strdup(argv[0]); + + return 0; + +} + +char** guac_copy_mimetypes(char** mimetypes, int count) { + + int i; + + /* Allocate sufficient space for NULL-terminated array of mimetypes */ + char** mimetypes_copy = malloc(sizeof(char*) * (count+1)); + + /* Copy each provided mimetype */ + for (i = 0; i < count; i++) + mimetypes_copy[i] = strdup(mimetypes[i]); + + /* Terminate with NULL */ + mimetypes_copy[count] = NULL; + + return mimetypes_copy; + +} + +void guac_free_mimetypes(char** mimetypes) { + + char** current_mimetype = mimetypes; + + /* Free all strings within NULL-terminated mimetype array */ + while (*current_mimetype != NULL) { + free(*current_mimetype); + current_mimetype++; + } + + /* Free the array itself, now that its contents have been freed */ + free(mimetypes); + +} + +int guac_user_handle_instruction(__guac_instruction_handler_mapping* map, + guac_user* user, const char* opcode, int argc, char** argv) { + + /* For each defined instruction */ + __guac_instruction_handler_mapping* current = map; + while (current->opcode != NULL) { + + /* If recognized, call handler */ + if (strcmp(opcode, current->opcode) == 0) + return current->handler(user, argc, argv); + + current++; + } + + /* If unrecognized, ignore */ + return 0; + +} + diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index 5d7c6eae..1a6477ed 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -177,6 +177,40 @@ __guac_instruction_handler __guac_handle_size; */ __guac_instruction_handler __guac_handle_disconnect; +/** + * Internal handler function that is called when the size instruction is + * received during the handshake process. + */ +__guac_instruction_handler __guac_handshake_size_handler; + +/** + * Internal handler function that is called when the audio instruction is + * received during the handshake process, specifying the audio mimetypes + * available to the client. + */ +__guac_instruction_handler __guac_handshake_audio_handler; + +/** + * Internal handler function that is called when the video instruction is + * received during the handshake process, specifying the video mimetypes + * available to the client. + */ +__guac_instruction_handler __guac_handshake_video_handler; + +/** + * Internal handler function that is called when the image instruction is + * received during the handshake process, specifying the image mimetypes + * available to the client. + */ +__guac_instruction_handler __guac_handshake_image_handler; + +/** + * Internal handler function that is called when the timezone instruction is + * received during the handshake process, specifying the timezone of the + * client. + */ +__guac_instruction_handler __guac_handshake_timezone_handler; + /** * Instruction handler mapping table. This is a NULL-terminated array of * __guac_instruction_handler_mapping structures, each mapping an opcode @@ -186,4 +220,71 @@ __guac_instruction_handler __guac_handle_disconnect; */ extern __guac_instruction_handler_mapping __guac_instruction_handler_map[]; +/** + * Handler mapping table for instructions (opcodes) specifically for the + * handshake portion of the connection. Each + * __guac_instruction_handler_mapping structure within this NULL-terminated + * array maps an opcode to a __guac_instruction_handler. The end of the array + * must be marked with a mapping with the opcode set to NULL. + */ +extern __guac_instruction_handler_mapping __guac_handshake_handler_map[]; + +/** + * Frees the given array of mimetypes, including the space allocated to each + * mimetype string within the array. The provided array of mimetypes MUST have + * been allocated with guac_copy_mimetypes(). + * + * @param mimetypes + * The NULL-terminated array of mimetypes to free. This array MUST have + * been previously allocated with guac_copy_mimetypes(). + */ +void guac_free_mimetypes(char** mimetypes); + +/** + * Copies the given array of mimetypes (strings) into a newly-allocated NULL- + * terminated array of strings. Both the array and the strings within the array + * are newly-allocated and must be later freed via guac_free_mimetypes(). + * + * @param mimetypes + * The array of mimetypes to copy. + * + * @param count + * The number of mimetypes in the given array. + * + * @return + * A newly-allocated, NULL-terminated array containing newly-allocated + * copies of each of the mimetypes provided in the original mimetypes + * array. + */ +char** guac_copy_mimetypes(char** mimetypes, int count); + +/** + * Call the appropriate handler defined by the given user for the given + * instruction. A comparison is made between the instruction opcode and the + * initial handler lookup table defined in user-handlers.c. The intial handlers + * will in turn call the user's handler (if defined). + * + * @param map + * The array that holds the opcode to handler mappings. + * + * @param user + * The user whose handlers should be called. + * + * @param opcode + * The opcode of the instruction to pass to the user via the appropriate + * handler. + * + * @param argc + * The number of arguments which are part of the instruction. + * + * @param argv + * An array of all arguments which are part of the instruction. + * + * @return + * Non-negative if the instruction was handled successfully, or negative + * if an error occurred. + */ +int guac_user_handle_instruction(__guac_instruction_handler_mapping* map, + guac_user* user, const char* opcode, int argc, char** argv); + #endif diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index e9ccbacd..9cccec4c 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -113,141 +113,6 @@ static void guac_user_log_handshake_failure(guac_user* user) { } -/** - * Copies the given array of mimetypes (strings) into a newly-allocated NULL- - * terminated array of strings. Both the array and the strings within the array - * are newly-allocated and must be later freed via guac_free_mimetypes(). - * - * @param mimetypes - * The array of mimetypes to copy. - * - * @param count - * The number of mimetypes in the given array. - * - * @return - * A newly-allocated, NULL-terminated array containing newly-allocated - * copies of each of the mimetypes provided in the original mimetypes - * array. - */ -static char** guac_copy_mimetypes(char** mimetypes, int count) { - - int i; - - /* Allocate sufficient space for NULL-terminated array of mimetypes */ - char** mimetypes_copy = malloc(sizeof(char*) * (count+1)); - - /* Copy each provided mimetype */ - for (i = 0; i < count; i++) - mimetypes_copy[i] = strdup(mimetypes[i]); - - /* Terminate with NULL */ - mimetypes_copy[count] = NULL; - - return mimetypes_copy; - -} - -/** - * Frees the given array of mimetypes, including the space allocated to each - * mimetype string within the array. The provided array of mimetypes MUST have - * been allocated with guac_copy_mimetypes(). - * - * @param mimetypes - * The NULL-terminated array of mimetypes to free. This array MUST have - * been previously allocated with guac_copy_mimetypes(). - */ -static void guac_free_mimetypes(char** mimetypes) { - - char** current_mimetype = mimetypes; - - /* Free all strings within NULL-terminated mimetype array */ - while (*current_mimetype != NULL) { - free(*current_mimetype); - current_mimetype++; - } - - /* Free the array itself, now that its contents have been freed */ - free(mimetypes); - -} - -/* Guacamole handshake handler functions. */ - -int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { - - /* Validate size of instruction. */ - if (argc < 2) { - guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" " - "instruction lacked required arguments."); - return 1; - } - - /* Parse optimal screen dimensions from size instruction */ - user->info.optimal_width = atoi(argv[0]); - user->info.optimal_height = atoi(argv[1]); - - /* If DPI given, set the user resolution */ - if (argc >= 3) - user->info.optimal_resolution = atoi(argv[2]); - - /* Otherwise, use a safe default for rough backwards compatibility */ - else - user->info.optimal_resolution = 96; - - return 0; - -} - -int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { - - /* Store audio mimetypes */ - user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); - - return 0; - -} - -int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { - - /* Store video mimetypes */ - user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); - - return 0; - -} - -int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { - - /* Store image mimetypes */ - user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); - - return 0; - -} - -int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { - - /* Free any past value */ - free((char *) user->info.timezone); - - /* Store timezone, if present */ - if (argc > 0 && strcmp(argv[0], "")) - user->info.timezone = (const char*) strdup(argv[0]); - - return 0; - -} - -/* Guacamole handshake handler mappings. */ -__guac_handshake_mapping __guac_handshake_map[] = { - {"size", __guac_handshake_size_handler}, - {"audio", __guac_handshake_audio_handler}, - {"video", __guac_handshake_video_handler}, - {"image", __guac_handshake_image_handler}, - {"timezone", __guac_handshake_timezone_handler}, - {NULL, NULL} -}; - /** * The thread which handles all user input, calling event handlers for received * instructions. @@ -296,7 +161,8 @@ static void* guac_user_input_thread(void* data) { guac_error_message = NULL; /* Call handler, stop on error */ - if (guac_user_handle_instruction(user, parser->opcode, parser->argc, parser->argv) < 0) { + if (guac_user_handle_instruction(__guac_instruction_handler_map, + user, parser->opcode, parser->argc, parser->argv) < 0) { /* Log error */ guac_user_log_guac_error(user, GUAC_LOG_WARNING, @@ -399,39 +265,21 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", parser->opcode); - /* Loop available opcodes and run handler if/when match found. */ - __guac_handshake_mapping* current = __guac_handshake_map; - while (current->opcode != NULL) { + /* Run instruction handler for opcode with arguments. */ + if (guac_user_handle_instruction(__guac_handshake_handler_map, user, + parser->opcode, parser->argc, parser->argv)) { - /* Check if loop opcode matches parsed opcode. */ - if (strcmp(parser->opcode, current->opcode) == 0) { - - /* If calling the handler fails, log it and return. */ - if (current->handler(user, parser->argc, parser->argv)) { - - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error handling handling opcode during handshake."); - guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", - current->opcode); + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error handling handling opcode during handshake."); + guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", + parser->opcode); - guac_parser_free(parser); - return 1; - } - - /* If calling the handler has succeeded, log it and break. */ - else { - guac_user_log(user, GUAC_LOG_DEBUG, - "Successfully processed instruction: \"%s\"", - current->opcode); - break; - } - - } + guac_parser_free(parser); + return 1; - /* Move to next opcode. */ - current++; } + } /* Acknowledge connection availability */ diff --git a/src/libguac/user.c b/src/libguac/user.c index 16590060..64c2a763 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -167,24 +167,6 @@ void guac_user_free_object(guac_user* user, guac_object* object) { } -int guac_user_handle_instruction(guac_user* user, const char* opcode, int argc, char** argv) { - - /* For each defined instruction */ - __guac_instruction_handler_mapping* current = __guac_instruction_handler_map; - while (current->opcode != NULL) { - - /* If recognized, call handler */ - if (strcmp(opcode, current->opcode) == 0) - return current->handler(user, argc, argv); - - current++; - } - - /* If unrecognized, ignore */ - return 0; - -} - void guac_user_stop(guac_user* user) { user->active = 0; } From c750b18f6030b7925760f00da9c205dbb96336b7 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 10 Apr 2019 07:08:20 -0400 Subject: [PATCH 129/175] GUACAMOLE-422: Handle sending version internally. --- src/libguac/guacamole/protocol.h | 3 +++ src/libguac/protocol.c | 5 +++++ src/protocols/kubernetes/settings.c | 8 -------- src/protocols/rdp/rdp_settings.c | 8 -------- src/protocols/ssh/settings.c | 8 -------- src/protocols/telnet/settings.c | 8 -------- src/protocols/vnc/settings.c | 8 -------- 7 files changed, 8 insertions(+), 40 deletions(-) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index d1a0420a..ff7746ff 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -38,6 +38,9 @@ #include #include +/* The protocol version */ +#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0" + /* CONTROL INSTRUCTIONS */ /** diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index ee266e87..f58179f3 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -93,6 +93,11 @@ static int __guac_protocol_send_args(guac_socket* socket, const char** args) { int i; if (guac_socket_write_string(socket, "4.args")) return -1; + + // Send protocol version + if (guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, GUACAMOLE_PROTOCOL_VERSION)) + return -1; for (i=0; args[i] != NULL; i++) { diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 6e8ddd41..ce7f6977 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -25,14 +25,6 @@ /* Client plugin arguments */ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { - /** - * This first argument defines the protocol version in use so that the - * client knows how to handle talking to different versions of guacd. - * If this is omitted the client may choose not to enable certain - * features. - */ - "VERSION_1_1_0", - "hostname", "port", "namespace", diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index 9edcb746..f45b3a43 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -42,14 +42,6 @@ /* Client plugin arguments */ const char* GUAC_RDP_CLIENT_ARGS[] = { - /** - * This first argument defines the protocol version in use so that the - * client knows how to handle talking to different versions of guacd. - * If this is omitted the client may choose not to enable certain - * features. - */ - "VERSION_1_1_0", - "hostname", "port", "domain", diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index a404e8ca..b69ddd6c 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -30,14 +30,6 @@ /* Client plugin arguments */ const char* GUAC_SSH_CLIENT_ARGS[] = { - /** - * This first argument defines the protocol version in use so that the - * client knows how to handle talking to different versions of guacd. - * If this is omitted the client may choose not to enable certain - * features. - */ - "VERSION_1_1_0", - "hostname", "host-key", "port", diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 4d2d4dd1..9d334df2 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -31,14 +31,6 @@ /* Client plugin arguments */ const char* GUAC_TELNET_CLIENT_ARGS[] = { - /** - * This first argument defines the protocol version in use so that the - * client knows how to handle talking to different versions of guacd. - * If this is omitted the client may choose not to enable certain - * features. - */ - "VERSION_1_1_0", - "hostname", "port", "username", diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 941a2757..c39aa730 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -31,14 +31,6 @@ /* Client plugin arguments */ const char* GUAC_VNC_CLIENT_ARGS[] = { - /** - * This first argument defines the protocol version in use so that the - * client knows how to handle talking to different versions of guacd. - * If this is omitted the client may choose not to enable certain - * features. - */ - "VERSION_1_1_0", - "hostname", "port", "read-only", From 379fce2d770608911e29fd3ca9bb1b9d9e3e1dbb Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Thu, 11 Apr 2019 17:11:41 -0400 Subject: [PATCH 130/175] GUACAMOLE-422: Rename new opcode handler function. --- src/libguac/guacamole/user.h | 26 ++++++++++++++++++++++++++ src/libguac/user-handlers.c | 2 +- src/libguac/user-handlers.h | 8 +++++--- src/libguac/user-handshake.c | 5 +++-- src/libguac/user.c | 7 +++++++ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 67ca7c88..f920c325 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -544,6 +544,32 @@ void guac_user_free(guac_user* user); */ int guac_user_handle_connection(guac_user* user, int usec_timeout); +/** + * Call the appropriate handler defined by the given user for the given + * instruction. A comparison is made between the instruction opcode and the + * initial handler lookup table defined in user-handlers.c. The initial handlers + * will in turn call the user's handler (if defined). + * + * @param user + * The user whose handlers should be called. + * + * @param opcode + * The opcode of the instruction to pass to the user via the appropriate + * handler. + * + * @param argc + * The number of arguments which are part of the instruction. + * + * @param argv + * An array of all arguments which are part of the instruction. + * + * @return + * Non-negative if the instruction was handled successfully, or negative + * if an error occurred. + */ +int guac_user_handle_instruction(guac_user* user, const char* opcode, + int argc, char** argv); + /** * Allocates a new stream. An arbitrary index is automatically assigned * if no previously-allocated stream is available for use. diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index 737e6f30..92dc249a 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -693,7 +693,7 @@ void guac_free_mimetypes(char** mimetypes) { } -int guac_user_handle_instruction(__guac_instruction_handler_mapping* map, +int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, guac_user* user, const char* opcode, int argc, char** argv) { /* For each defined instruction */ diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index 1a6477ed..dd9f829b 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -261,8 +261,10 @@ char** guac_copy_mimetypes(char** mimetypes, int count); /** * Call the appropriate handler defined by the given user for the given * instruction. A comparison is made between the instruction opcode and the - * initial handler lookup table defined in user-handlers.c. The intial handlers - * will in turn call the user's handler (if defined). + * initial handler lookup table defined in the map that is provided to this + * function. If an entry for the instruction is found in the provided map, + * the handler defined in that map will be called and the value returned. If + * no match is found, it is silently ignored. * * @param map * The array that holds the opcode to handler mappings. @@ -284,7 +286,7 @@ char** guac_copy_mimetypes(char** mimetypes, int count); * Non-negative if the instruction was handled successfully, or negative * if an error occurred. */ -int guac_user_handle_instruction(__guac_instruction_handler_mapping* map, +int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, guac_user* user, const char* opcode, int argc, char** argv); #endif diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 9cccec4c..cf96d347 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -25,6 +25,7 @@ #include "guacamole/protocol.h" #include "guacamole/socket.h" #include "guacamole/user.h" +#include "user-handlers.h" #include #include @@ -161,7 +162,7 @@ static void* guac_user_input_thread(void* data) { guac_error_message = NULL; /* Call handler, stop on error */ - if (guac_user_handle_instruction(__guac_instruction_handler_map, + if (__guac_user_call_opcode_handler(__guac_instruction_handler_map, user, parser->opcode, parser->argc, parser->argv) < 0) { /* Log error */ @@ -266,7 +267,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { parser->opcode); /* Run instruction handler for opcode with arguments. */ - if (guac_user_handle_instruction(__guac_handshake_handler_map, user, + if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user, parser->opcode, parser->argc, parser->argv)) { guac_user_log_handshake_failure(user); diff --git a/src/libguac/user.c b/src/libguac/user.c index 64c2a763..9145b275 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -167,6 +167,13 @@ void guac_user_free_object(guac_user* user, guac_object* object) { } +int guac_user_handle_instruction(guac_user* user, const char* opcode, int argc, char** argv) { + + return __guac_user_call_opcode_handler(__guac_instruction_handler_map, + user, opcode, argc, argv); + +} + void guac_user_stop(guac_user* user) { user->active = 0; } From 340aef5362ae013ca38e3a8cbe1bfcdf53746775 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 13 Apr 2019 21:30:57 -0400 Subject: [PATCH 131/175] GUACAMOLE-422: Update to remove first argument --- src/libguac/user-handshake.c | 9 +++++---- src/protocols/kubernetes/settings.c | 5 ----- src/protocols/rdp/rdp_settings.c | 5 ----- src/protocols/ssh/settings.c | 5 ----- src/protocols/telnet/settings.c | 5 ----- src/protocols/vnc/settings.c | 5 ----- 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index cf96d347..a7805b21 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -234,7 +234,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_socket* socket = user->socket; guac_client* client = user->client; - + /* Send args */ if (guac_protocol_send_args(socket, client->args) || guac_socket_flush(socket)) { @@ -286,9 +286,10 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { /* Acknowledge connection availability */ guac_protocol_send_ready(socket, client->connection_id); guac_socket_flush(socket); - - /* Attempt join */ - if (guac_client_add_user(client, user, parser->argc, parser->argv)) + + /* Check return value from join attempt */ + if (guac_client_add_user(client, user, (parser->argc - 1), + parser->argv + 1)) guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT " "join connection \"%s\"", user->user_id, client->connection_id); diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index ce7f6977..0ae3dc23 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -54,11 +54,6 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { }; enum KUBERNETES_ARGS_IDX { - - /** - * The protocol version provided to the client. - */ - IDX_PROTOCOL_VERSION, /** * The hostname to connect to. Required. diff --git a/src/protocols/rdp/rdp_settings.c b/src/protocols/rdp/rdp_settings.c index f45b3a43..51214a61 100644 --- a/src/protocols/rdp/rdp_settings.c +++ b/src/protocols/rdp/rdp_settings.c @@ -123,11 +123,6 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { }; enum RDP_ARGS_IDX { - - /** - * The protocol version sent to the client. - */ - IDX_PROTOCOL_VERSION, /** * The hostname to connect to. diff --git a/src/protocols/ssh/settings.c b/src/protocols/ssh/settings.c index b69ddd6c..a0af9f9b 100644 --- a/src/protocols/ssh/settings.c +++ b/src/protocols/ssh/settings.c @@ -66,11 +66,6 @@ const char* GUAC_SSH_CLIENT_ARGS[] = { }; enum SSH_ARGS_IDX { - - /** - * The protocol version - */ - IDX_PROTOCOL_VERSION, /** * The hostname to connect to. Required. diff --git a/src/protocols/telnet/settings.c b/src/protocols/telnet/settings.c index 9d334df2..85b4fb85 100644 --- a/src/protocols/telnet/settings.c +++ b/src/protocols/telnet/settings.c @@ -59,11 +59,6 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = { }; enum TELNET_ARGS_IDX { - - /** - * The protocol version provided by the client. - */ - IDX_PROTOCOL_VERSION, /** * The hostname to connect to. Required. diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index c39aa730..623e6688 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -82,11 +82,6 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { }; enum VNC_ARGS_IDX { - - /** - * The protocol version provided to the client. - */ - IDX_PROTOCOL_VERSION, /** * The hostname of the VNC server (or repeater) to connect to. From 54f88531d43b0eb7fd54543651844f6fc12d3df8 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 17 Apr 2019 14:56:51 -0400 Subject: [PATCH 132/175] GUACAMOLE-422: Try to avoid memory leaks with mimetypes. --- src/libguac/user-handlers.c | 9 +++++++++ src/libguac/user-handshake.c | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index 92dc249a..f1d937e3 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -622,6 +622,8 @@ int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { + guac_free_mimetypes((char **) user->info.audio_mimetypes); + /* Store audio mimetypes */ user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); @@ -631,6 +633,8 @@ int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { + guac_free_mimetypes((char **) user->info.video_mimetypes); + /* Store video mimetypes */ user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); @@ -640,6 +644,8 @@ int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) { + guac_free_mimetypes((char **) user->info.image_mimetypes); + /* Store image mimetypes */ user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc); @@ -680,6 +686,9 @@ char** guac_copy_mimetypes(char** mimetypes, int count) { void guac_free_mimetypes(char** mimetypes) { + if (mimetypes == NULL) + return; + char** current_mimetype = mimetypes; /* Free all strings within NULL-terminated mimetype array */ diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index a7805b21..8c1fd89b 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -235,6 +235,10 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_socket* socket = user->socket; guac_client* client = user->client; + user->info.audio_mimetypes = NULL; + user->info.image_mimetypes = NULL; + user->info.video_mimetypes = NULL; + /* Send args */ if (guac_protocol_send_args(socket, client->args) || guac_socket_flush(socket)) { From 75c0deac1f445fdd81f768972d5cd5340352982a Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 17 Apr 2019 15:03:27 -0400 Subject: [PATCH 133/175] GUACAMOLE-422: Update comment style for consistency. --- src/libguac/protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index f58179f3..46a9858e 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -94,7 +94,7 @@ static int __guac_protocol_send_args(guac_socket* socket, const char** args) { if (guac_socket_write_string(socket, "4.args")) return -1; - // Send protocol version + /* Send protocol version ahead of other args. */ if (guac_socket_write_string(socket, ",") || __guac_socket_write_length_string(socket, GUACAMOLE_PROTOCOL_VERSION)) return -1; From 98cb7ccf6773f9e55645ccfe542de31ab2bc136c Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 17 Apr 2019 15:12:46 -0400 Subject: [PATCH 134/175] GUACAMOLE-422: Fix comment and style. --- src/libguac/user-handshake.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 8c1fd89b..ed30c63a 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -291,9 +291,8 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_protocol_send_ready(socket, client->connection_id); guac_socket_flush(socket); - /* Check return value from join attempt */ - if (guac_client_add_user(client, user, (parser->argc - 1), - parser->argv + 1)) + /* Attempt to join user to connection. */ + if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1)) guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT " "join connection \"%s\"", user->user_id, client->connection_id); From ab12b2aa8e3134d42c5fd42432d95eeec9be2a77 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 19 Apr 2019 15:48:14 -0400 Subject: [PATCH 135/175] GUACAMOLE-422: More substantial comment for protocol version; NULL out timezone at beginning of handshake. --- src/libguac/guacamole/protocol.h | 13 ++++++++++++- src/libguac/user-handshake.c | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index ff7746ff..0b462b4c 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -38,7 +38,18 @@ #include #include -/* The protocol version */ +/** + * This defines the overall protocol version that this build of libguac + * supports. The protocol version is used to provide compatibility between + * potentially different versions of Guacamole server and clients. The + * version number is a MAJOR_MINOR_PATCH version that matches the versioning + * used throughout the components of the Guacamole project. This version + * will not necessarily increment with the other components, unless additional + * functionality is introduced that affects compatibility. + * + * This version is passed by the __guac_protocol_send_args() function from the + * server to the client during the client/server handshake. + */ #define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0" /* CONTROL INSTRUCTIONS */ diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index ed30c63a..26b42a76 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -238,6 +238,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { user->info.audio_mimetypes = NULL; user->info.image_mimetypes = NULL; user->info.video_mimetypes = NULL; + user->info.timezone = NULL; /* Send args */ if (guac_protocol_send_args(socket, client->args) From 588e0f194a7a90cc92e900dc89e66bb566d304dd Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 19 Apr 2019 15:50:29 -0400 Subject: [PATCH 136/175] GUACAMOLE-422: Fix return documentation on opcode handler function. --- src/libguac/user-handlers.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index dd9f829b..17cfc0db 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -283,8 +283,7 @@ char** guac_copy_mimetypes(char** mimetypes, int count); * An array of all arguments which are part of the instruction. * * @return - * Non-negative if the instruction was handled successfully, or negative - * if an error occurred. + * Zero if the instruction was handled successfully, or non-zero otherwise. */ int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, guac_user* user, const char* opcode, int argc, char** argv); From 4b43de963e998e874051b1895a44436a903f0565 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 22 Apr 2019 11:49:46 -0400 Subject: [PATCH 137/175] GUACAMOLE-422: Break handshake out into separate function; NULL out timezone when not received. --- src/libguac/user-handlers.c | 3 ++ src/libguac/user-handshake.c | 94 ++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index f1d937e3..cef6c14c 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -662,6 +662,9 @@ int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { if (argc > 0 && strcmp(argv[0], "")) user->info.timezone = (const char*) strdup(argv[0]); + else + user->info.timezone = NULL; + return 0; } diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 26b42a76..31f9d9fb 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -230,6 +230,62 @@ static int guac_user_start(guac_parser* parser, guac_user* user, } +/** + * This function loops through the received instructions during the handshake + * with the client attempting to join the connection, and runs the handlers + * for each of the opcodes, ending when the connect instruction is received. + * Returns zero if the handshake completes successfully with the connect opcode, + * or a non-zero value if an error occurs. + * + * @param user + * The guac_user attempting to join the connection. + * + * @param parser + * The parser used to examine the received data. + * + * @param usec_timeout + * The timeout, in microseconds, for reading the instructions. + * + * @return + * Zero if the handshake completes successfully with the connect opcode, + * or non-zero if an error occurs. + */ +static int __guac_user_handshake(guac_user* user, guac_parser* parser, + int usec_timeout) { + + guac_socket* socket = user->socket; + + /* Handle each of the opcodes. */ + while (guac_parser_read(parser, socket, usec_timeout) == 0) { + + /* If we receive the connect opcode, we're done. */ + if (strcmp(parser->opcode, "connect") == 0) + return 0; + + guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", + parser->opcode); + + /* Run instruction handler for opcode with arguments. */ + if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user, + parser->opcode, parser->argc, parser->argv)) { + + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error handling handling opcode during handshake."); + guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", + parser->opcode); + + guac_parser_free(parser); + return 1; + + } + + } + + /* If we get here something has gone wrong. */ + return 1; +} + int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_socket* socket = user->socket; @@ -254,38 +310,12 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_parser* parser = guac_parser_alloc(); - /* Handle each of the opcodes. */ - while (1) { - if (guac_parser_read(parser, socket, usec_timeout)) { - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error while reading opcode instruction."); - guac_parser_free(parser); - return 1; - } - - /* If we receive the connect opcode, we're done. */ - if (strcmp(parser->opcode, "connect") == 0) - break; - - guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s", - parser->opcode); - - /* Run instruction handler for opcode with arguments. */ - if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user, - parser->opcode, parser->argc, parser->argv)) { - - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error handling handling opcode during handshake."); - guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", - parser->opcode); - - guac_parser_free(parser); - return 1; - - } - + /* Perform the handshake with the client. */ + if (__guac_user_handshake(user, parser, usec_timeout)) { + guac_user_log_handshake_failure(user); + guac_user_log_guac_error(user, GUAC_LOG_DEBUG, + "Error while reading opcode instruction."); + guac_parser_free(parser); } /* Acknowledge connection availability */ From 93a240b8adcaac88cbea0de9847ac058f029c53e Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 24 Apr 2019 22:21:18 -0400 Subject: [PATCH 138/175] GUACAMOLE-422: Add debugging and check argument count. --- src/libguac/user-handlers.c | 4 +++- src/libguac/user-handshake.c | 29 +++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index cef6c14c..fd8540c5 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -719,7 +719,9 @@ int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, current++; } - /* If unrecognized, ignore */ + /* If unrecognized, log and ignore */ + guac_user_log(user, GUAC_LOG_WARNING, "Handler not found for \"%s\"", + opcode); return 0; } diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 31f9d9fb..a2d8ec4a 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -163,7 +163,7 @@ static void* guac_user_input_thread(void* data) { /* Call handler, stop on error */ if (__guac_user_call_opcode_handler(__guac_instruction_handler_map, - user, parser->opcode, parser->argc, parser->argv) < 0) { + user, parser->opcode, parser->argc, parser->argv)) { /* Log error */ guac_user_log_guac_error(user, GUAC_LOG_WARNING, @@ -271,7 +271,7 @@ static int __guac_user_handshake(guac_user* user, guac_parser* parser, guac_user_log_handshake_failure(user); guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error handling handling opcode during handshake."); + "Error handling instruction during handshake."); guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s", parser->opcode); @@ -282,7 +282,9 @@ static int __guac_user_handshake(guac_user* user, guac_parser* parser, } - /* If we get here something has gone wrong. */ + /* If we get here it's because we never got the connect instruction. */ + guac_user_log(user, GUAC_LOG_ERROR, + "Handshake failed, \"connect\" instruction was not received."); return 1; } @@ -296,6 +298,10 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { user->info.video_mimetypes = NULL; user->info.timezone = NULL; + /* Count number of arguments. */ + int numArgs; + for (numArgs = 0; client->args[numArgs] != NULL; numArgs++); + /* Send args */ if (guac_protocol_send_args(socket, client->args) || guac_socket_flush(socket)) { @@ -312,16 +318,21 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { /* Perform the handshake with the client. */ if (__guac_user_handshake(user, parser, usec_timeout)) { - guac_user_log_handshake_failure(user); - guac_user_log_guac_error(user, GUAC_LOG_DEBUG, - "Error while reading opcode instruction."); guac_parser_free(parser); + return 1; } /* Acknowledge connection availability */ guac_protocol_send_ready(socket, client->connection_id); guac_socket_flush(socket); + /* Verify argument count. */ + if (parser->argc != (numArgs + 1)) { + guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the " + "expected number of arguments."); + return 1; + } + /* Attempt to join user to connection. */ if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1)) guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT " @@ -333,6 +344,12 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection " "\"%s\" (%i users now present)", user->user_id, client->connection_id, client->connected_users); + if (strcmp(parser->argv[0],"") != 0) + guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol " + "version \"%s\"", parser->argv[0]); + else + guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined " + "its protocol version."); /* Handle user I/O, wait for connection to terminate */ guac_user_start(parser, user, usec_timeout); From bf741a46d6fc4de1afc3b013c6094ba9893df881 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 27 Apr 2019 21:37:26 -0400 Subject: [PATCH 139/175] GUACAMOLE-422: Minor style and debug changes. --- src/libguac/user-handlers.c | 2 +- src/libguac/user-handshake.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index fd8540c5..eeed9bcd 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -720,7 +720,7 @@ int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, } /* If unrecognized, log and ignore */ - guac_user_log(user, GUAC_LOG_WARNING, "Handler not found for \"%s\"", + guac_user_log(user, GUAC_LOG_DEBUG, "Handler not found for \"%s\"", opcode); return 0; diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index a2d8ec4a..89afccdd 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -299,8 +299,8 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { user->info.timezone = NULL; /* Count number of arguments. */ - int numArgs; - for (numArgs = 0; client->args[numArgs] != NULL; numArgs++); + int num_args; + for (num_args = 0; client->args[num_args] != NULL; num_args++); /* Send args */ if (guac_protocol_send_args(socket, client->args) @@ -327,7 +327,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) { guac_socket_flush(socket); /* Verify argument count. */ - if (parser->argc != (numArgs + 1)) { + if (parser->argc != (num_args + 1)) { guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the " "expected number of arguments."); return 1; From b7dca0ed16dda407f10a9d88fe86b9184ff4522b Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 15:16:22 -0500 Subject: [PATCH 140/175] GUACAMOLE-547: Add support for SSH NONE authentication method. --- src/common-ssh/ssh.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 9dde5111..2763840e 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -321,6 +321,10 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist); + /* If auth list is NULL, then authentication has succeeded with NONE */ + if (user_authlist == NULL) + return 0; + /* Authenticate with private key, if provided */ if (key != NULL) { From 4641da06aca96c04ed2b165627d35a710793ac90 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 11 Nov 2018 16:11:06 -0500 Subject: [PATCH 141/175] GUACAMOLE-547: Relocate NULL check and log when NONE succeeds. --- src/common-ssh/ssh.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 2763840e..7691bf5d 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -318,12 +318,17 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) /* Get list of supported authentication methods */ char* user_authlist = libssh2_userauth_list(session, username, strlen(username)); + + /* If auth list is NULL, then authentication has succeeded with NONE */ + if (user_authlist == NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, + "SSH NONE authentication succeeded."); + return 0; + } + guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist); - /* If auth list is NULL, then authentication has succeeded with NONE */ - if (user_authlist == NULL) - return 0; /* Authenticate with private key, if provided */ if (key != NULL) { From 3d15454097d61156e6a0306caf12ac262ba11968 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Thu, 27 Dec 2018 01:01:06 -0500 Subject: [PATCH 142/175] GUACAMOLE-547: Use a call-back function for getting the password. --- src/common-ssh/common-ssh/ssh.h | 12 +++++++++++- src/common-ssh/ssh.c | 23 +++++++++++++---------- src/protocols/rdp/rdp.c | 2 +- src/protocols/ssh/ssh.c | 24 +++++++++--------------- src/protocols/vnc/vnc.c | 2 +- 5 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index 672e7767..ecc1c4b3 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -25,6 +25,11 @@ #include #include +/** + * Handler for retrieving additional credentials. + */ +typedef char* guac_ssh_credential_handler(guac_client* client, char* credName); + /** * An SSH session, backed by libssh2 and associated with a particular * Guacamole client. @@ -50,6 +55,11 @@ typedef struct guac_common_ssh_session { * The file descriptor of the socket being used for the SSH connection. */ int fd; + + /** + * Callback function to retrieve credentials. + */ + guac_ssh_credential_handler* credential_handler; } guac_common_ssh_session; @@ -99,7 +109,7 @@ void guac_common_ssh_uninit(); */ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive, - const char* host_key); + const char* host_key, guac_ssh_credential_handler* credential_callback); /** * Disconnects and destroys the given SSH session, freeing all associated diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 7691bf5d..3e6d9346 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -304,20 +304,18 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) LIBSSH2_SESSION* session = common_session->session; /* Get user credentials */ - char* username = user->username; - char* password = user->password; guac_common_ssh_key* key = user->private_key; /* Validate username provided */ - if (username == NULL) { + if (user->username == NULL) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, "SSH authentication requires a username."); return 1; } /* Get list of supported authentication methods */ - char* user_authlist = libssh2_userauth_list(session, username, - strlen(username)); + char* user_authlist = libssh2_userauth_list(session, user->username, + strlen(user->username)); /* If auth list is NULL, then authentication has succeeded with NONE */ if (user_authlist == NULL) { @@ -342,7 +340,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } /* Attempt public key auth */ - if (libssh2_userauth_publickey(session, username, + if (libssh2_userauth_publickey(session, user->username, (unsigned char*) key->public_key, key->public_key_length, guac_common_ssh_sign_callback, (void**) key)) { @@ -361,14 +359,18 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } + /* Down to username + password authentication. */ + if (user->password == NULL && common_session->credential_handler) + user->password = common_session->credential_handler(client, "Password: "); + /* Authenticate with password, if provided */ - else if (password != NULL) { + if (user->password != NULL) { /* Check if password auth is supported on the server */ if (strstr(user_authlist, "password") != NULL) { /* Attempt password authentication */ - if (libssh2_userauth_password(session, username, password)) { + if (libssh2_userauth_password(session, user->username, user->password)) { /* Abort on failure */ char* error_message; @@ -389,7 +391,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) if (strstr(user_authlist, "keyboard-interactive") != NULL) { /* Attempt keyboard-interactive auth using provided password */ - if (libssh2_userauth_keyboard_interactive(session, username, + if (libssh2_userauth_keyboard_interactive(session, user->username, &guac_common_ssh_kbd_callback)) { /* Abort on failure */ @@ -425,7 +427,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive, - const char* host_key) { + const char* host_key, guac_ssh_credential_handler* credential_handler) { int retval; @@ -570,6 +572,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, common_session->user = user; common_session->session = session; common_session->fd = fd; + common_session->credential_handler = credential_handler; /* Attempt authentication */ if (guac_common_ssh_authenticate(common_session)) { diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 042d3893..86f50967 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -975,7 +975,7 @@ void* guac_rdp_client_thread(void* data) { rdp_client->sftp_session = guac_common_ssh_create_session(client, settings->sftp_hostname, settings->sftp_port, rdp_client->sftp_user, settings->sftp_server_alive_interval, - settings->sftp_host_key); + settings->sftp_host_key, NULL); /* Fail if SSH connection does not succeed */ if (rdp_client->sftp_session == NULL) { diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 9db545bf..0b7dd320 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -130,19 +130,6 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } /* end if key given */ - /* Otherwise, use password */ - else { - - /* Get password if not provided */ - if (settings->password == NULL) - settings->password = guac_terminal_prompt(ssh_client->term, - "Password: ", false); - - /* Set provided password */ - guac_common_ssh_user_set_password(user, settings->password); - - } - /* Clear screen of any prompts */ guac_terminal_printf(ssh_client->term, "\x1B[H\x1B[J"); @@ -150,6 +137,13 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } +char* guac_ssh_get_credential(guac_client *client, char* credName) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + return guac_terminal_prompt(ssh_client->term, credName, false); + +} + void* ssh_input_thread(void* data) { guac_client* client = (guac_client*) data; @@ -239,7 +233,7 @@ void* ssh_client_thread(void* data) { /* Open SSH session */ ssh_client->session = guac_common_ssh_create_session(client, settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval, - settings->host_key); + settings->host_key, guac_ssh_get_credential); if (ssh_client->session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; @@ -291,7 +285,7 @@ void* ssh_client_thread(void* data) { ssh_client->sftp_session = guac_common_ssh_create_session(client, settings->hostname, settings->port, ssh_client->user, settings->server_alive_interval, - settings->host_key); + settings->host_key, NULL); if (ssh_client->sftp_session == NULL) { /* Already aborted within guac_common_ssh_create_session() */ return NULL; diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index d9f9dbbb..afffaac4 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -262,7 +262,7 @@ void* guac_vnc_client_thread(void* data) { vnc_client->sftp_session = guac_common_ssh_create_session(client, settings->sftp_hostname, settings->sftp_port, vnc_client->sftp_user, settings->sftp_server_alive_interval, - settings->sftp_host_key); + settings->sftp_host_key, NULL); /* Fail if SSH connection does not succeed */ if (vnc_client->sftp_session == NULL) { From 9a51d513f244c21b10d00c0f6f73d2232dd40370 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Thu, 27 Dec 2018 13:07:02 -0500 Subject: [PATCH 143/175] GUACAMOLE-547: Provide documentation for the new callback function. --- src/protocols/ssh/ssh.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 0b7dd320..63361e6e 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -137,6 +137,22 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } +/** + * A call-back function used to gather additional credentials from a client + * during a connection. It takes the guac_client object and a string to + * display to the user, and returns the credentials entered by the user. + * + * @param client + * The guac_client object associated with the current connection + * where additional credentials are required. + * + * @param credName + * The prompt text to display to the screen when prompting for the + * additional credentials. + * + * @return + * The string of credentials gathered from the user. + */ char* guac_ssh_get_credential(guac_client *client, char* credName) { guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; From 3511991e2fcaf354ff9ddc726153c759861901a7 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 18 Jun 2019 07:52:05 -0400 Subject: [PATCH 144/175] GUACAMOLE-547: Fixes for style and documentation. --- src/common-ssh/common-ssh/ssh.h | 32 +++++++++++++++++++++++++++++--- src/common-ssh/ssh.c | 8 ++++---- src/protocols/ssh/ssh.c | 12 ++++++------ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index ecc1c4b3..73b86d6e 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -27,8 +27,17 @@ /** * Handler for retrieving additional credentials. + * + * @param client + * The Guacamole Client associated with this need for additional + * credentials. + * + * @param cred_name + * The name of the credential being requested, which will be shared + * with the client in order to generate a meaningful prompt. + * */ -typedef char* guac_ssh_credential_handler(guac_client* client, char* credName); +typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name); /** * An SSH session, backed by libssh2 and associated with a particular @@ -102,14 +111,31 @@ void guac_common_ssh_uninit(); * * @param user * The user to authenticate as, once connected. + * + * @param keepalive + * How frequently the connection should send keepalive packets, in + * seconds. Zero disables keepalive packets, and 2 is the minimum + * configurable value. + * + * @param host_key + * The known public host key of the server, as provided by the client. If + * provided the identity of the server will be checked against this key, + * and a mis-match between this and the server identity will cause the + * connection to fail. If not provided, no checks will be done and the + * connection will proceed. + * + * @param credential_handler + * The handler function for retrieving additional credentials from the user + * as required by the SSH server. * * @return * A new SSH session if the connection and authentication succeed, or NULL * if the connection or authentication were not successful. */ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive, - const char* host_key, guac_ssh_credential_handler* credential_callback); + const char* hostname, const char* port, guac_common_ssh_user* user, + int keepalive, const char* host_key, + guac_ssh_credential_handler* credential_handler); /** * Disconnects and destroys the given SSH session, freeing all associated diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index 3e6d9346..c03e984d 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -327,7 +327,6 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) guac_client_log(client, GUAC_LOG_DEBUG, "Supported authentication methods: %s", user_authlist); - /* Authenticate with private key, if provided */ if (key != NULL) { @@ -359,7 +358,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } - /* Down to username + password authentication. */ + /* Attempt authentication with username + password. */ if (user->password == NULL && common_session->credential_handler) user->password = common_session->credential_handler(client, "Password: "); @@ -426,8 +425,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, - const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive, - const char* host_key, guac_ssh_credential_handler* credential_handler) { + const char* hostname, const char* port, guac_common_ssh_user* user, + int keepalive, const char* host_key, + guac_ssh_credential_handler* credential_handler) { int retval; diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 63361e6e..0a67be08 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -138,25 +138,25 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } /** - * A call-back function used to gather additional credentials from a client - * during a connection. It takes the guac_client object and a string to - * display to the user, and returns the credentials entered by the user. + * A function used to generate a terminal prompt to gather additional + * credentials from the guac_client during a connection, and using + * the specified string to generate the prompt for the user. * * @param client * The guac_client object associated with the current connection * where additional credentials are required. * - * @param credName + * @param cred_name * The prompt text to display to the screen when prompting for the * additional credentials. * * @return * The string of credentials gathered from the user. */ -char* guac_ssh_get_credential(guac_client *client, char* credName) { +static char* guac_ssh_get_credential(guac_client *client, char* cred_name) { guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; - return guac_terminal_prompt(ssh_client->term, credName, false); + return guac_terminal_prompt(ssh_client->term, cred_name, false); } From 22874e23881bb35be08970f362c8ae50d5fc3ec7 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 18 Jun 2019 17:59:33 -0400 Subject: [PATCH 145/175] GUACAMOLE-547: Document return value of credential handler. --- src/common-ssh/common-ssh/ssh.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index 73b86d6e..62475d60 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -36,6 +36,9 @@ * The name of the credential being requested, which will be shared * with the client in order to generate a meaningful prompt. * + * @return + * The credential provided by the user, which should be a dynamically- + * allocated such that it can be freed as required. */ typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name); From 1baa91f85249cdf12a735a12e46cea353611520b Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Wed, 19 Jun 2019 12:38:05 -0400 Subject: [PATCH 146/175] GUACAMOLE-547: Minor changes to function documentation. --- src/common-ssh/common-ssh/ssh.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common-ssh/common-ssh/ssh.h b/src/common-ssh/common-ssh/ssh.h index 62475d60..346d8abc 100644 --- a/src/common-ssh/common-ssh/ssh.h +++ b/src/common-ssh/common-ssh/ssh.h @@ -37,8 +37,8 @@ * with the client in order to generate a meaningful prompt. * * @return - * The credential provided by the user, which should be a dynamically- - * allocated such that it can be freed as required. + * A newly-allocated string containing the credentials provided by + * the user, which must be freed by a call to free(). */ typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name); @@ -129,7 +129,8 @@ void guac_common_ssh_uninit(); * * @param credential_handler * The handler function for retrieving additional credentials from the user - * as required by the SSH server. + * as required by the SSH server, or NULL if the user will not be asked + * for additional credentials. * * @return * A new SSH session if the connection and authentication succeed, or NULL From badee3274bb773fc94060c3dea6aff2c0d4c9e14 Mon Sep 17 00:00:00 2001 From: P-Zs <52539371+P-Zs@users.noreply.github.com> Date: Thu, 4 Jul 2019 17:59:38 +0200 Subject: [PATCH 147/175] GUACAMOLE-837: Add Hungarian RDP keymap file to code base --- src/protocols/rdp/keymaps/hu_hu_qwertz.keymap | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/protocols/rdp/keymaps/hu_hu_qwertz.keymap diff --git a/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap b/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap new file mode 100644 index 00000000..bcb6a67c --- /dev/null +++ b/src/protocols/rdp/keymaps/hu_hu_qwertz.keymap @@ -0,0 +1,100 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "hu-hu-qwertz" +freerdp "KBD_HUNGARIAN" + +# +# Basic keys +# + +map -altgr -shift 0x29 0x02..0x0D ~ "0123456789öüó" +map -altgr -shift 0x10..0x1B ~ "qwertzuıopőú" +map -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjkléáű" +map -altgr -shift 0x56 0x2C..0x35 ~ "íyxcvbnm,.-" + +map -altgr +shift 0x29 0x02..0x0D ~ "§'"+!%/=()ÖÜÓ" +map -altgr +shift 0x10..0x1B ~ "QWERTZUIOPŐÚ" +map -altgr +shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÉÁŰ" +map -altgr +shift 0x56 0x2C..0x35 ~ "ÍYXCVBNM?:_" + + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02 ~ "~" +map +altgr -shift 0x08 ~ "`" + +map +altgr -shift 0x10 ~ "\" +map +altgr -shift 0x11 ~ "|" +map +altgr -shift 0x12 ~ "Ä" +map +altgr -shift 0x16 ~ "€" +map +altgr -shift 0x17 ~ "Í" +map +altgr -shift 0x1A ~ "÷" +map +altgr -shift 0x1B ~ "×" + +map +altgr -shift 0x1E ~ "ä" +map +altgr -shift 0x1F ~ "đ" +map +altgr -shift 0x20 ~ "Đ" +map +altgr -shift 0x21 ~ "[" +map +altgr -shift 0x22 ~ "]" +map +altgr -shift 0x24 ~ "í" +map +altgr -shift 0x25 ~ "ł" +map +altgr -shift 0x26 ~ "Ł" +map +altgr -shift 0x27 ~ "$" +map +altgr -shift 0x28 ~ "ß" +map +altgr -shift 0x2B ~ "¤" + +map +altgr -shift 0x56 ~ "<" +map +altgr -shift 0x2C ~ ">" +map +altgr -shift 0x2D ~ "#" +map +altgr -shift 0x2E ~ "&" +map +altgr -shift 0x2F ~ "@" +map +altgr -shift 0x30 ~ "{" +map +altgr -shift 0x31 ~ "}" +map +altgr -shift 0x32 ~ "<" +map +altgr -shift 0x33 ~ ";" +map +altgr -shift 0x34 ~ ">" +map +altgr -shift 0x35 ~ "*" + + +# +# Keys requiring AltGr & Shift +# + + +# +# Dead keys +# + +map +altgr -shift 0x03 ~ 0xFE5A # Dead caron +map +altgr -shift 0x04 ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x05 ~ 0xFE55 # Dead breve +map +altgr -shift 0x06 ~ 0xFE58 # Dead abovering +map +altgr -shift 0x07 ~ 0xFE5C # Dead ogonek +map +altgr -shift 0x09 ~ 0xFE56 # Dead abovedot +map +altgr -shift 0x0A ~ 0xFE51 # Dead acute +map +altgr -shift 0x0B ~ 0xFE59 # Dead doubleacute +map +altgr -shift 0x0C ~ 0xFE57 # Dead diaeresis +map +altgr -shift 0x0D ~ 0xFE5B # Dead cedilla + + +# END From 8f1826d3e4dbc13f757b846f9e692f8d557b7cd1 Mon Sep 17 00:00:00 2001 From: P-Zs <52539371+P-Zs@users.noreply.github.com> Date: Thu, 4 Jul 2019 18:01:02 +0200 Subject: [PATCH 148/175] GUACAMOLE-837: Update Makefile.am to reference the new Hungarian keymap file --- src/protocols/rdp/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index b7b8d2f9..d679a713 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -270,6 +270,7 @@ rdp_keymaps = \ $(srcdir)/keymaps/es_es_qwerty.keymap \ $(srcdir)/keymaps/fr_fr_azerty.keymap \ $(srcdir)/keymaps/fr_ch_qwertz.keymap \ + $(srcdir)/keymaps/hu_hu_qwertz.keymap \ $(srcdir)/keymaps/it_it_qwerty.keymap \ $(srcdir)/keymaps/ja_jp_qwerty.keymap \ $(srcdir)/keymaps/pt_br_qwerty.keymap \ From 9228d2637a4a43e411d0f153d67fe05e8602aba9 Mon Sep 17 00:00:00 2001 From: fgiuba Date: Fri, 5 Jul 2019 17:51:36 +0200 Subject: [PATCH 149/175] GUACAMOLE-547: Do not ignore password from settings. --- src/protocols/ssh/ssh.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 0a67be08..82d0507d 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -130,6 +130,11 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } /* end if key given */ + /* If available, get password from settings */ + else if (settings->password != NULL){ + guac_common_ssh_user_set_password(user, settings->password); + } + /* Clear screen of any prompts */ guac_terminal_printf(ssh_client->term, "\x1B[H\x1B[J"); From 55add063c50b308c395fc6d6471333758383f7ef Mon Sep 17 00:00:00 2001 From: fgiuba Date: Thu, 11 Jul 2019 09:37:17 +0200 Subject: [PATCH 150/175] GUACAMOLE-547: Add missing space between brackets. --- src/protocols/ssh/ssh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 82d0507d..8a18cf4d 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -131,7 +131,7 @@ static guac_common_ssh_user* guac_ssh_get_user(guac_client* client) { } /* end if key given */ /* If available, get password from settings */ - else if (settings->password != NULL){ + else if (settings->password != NULL) { guac_common_ssh_user_set_password(user, settings->password); } From 87a5479ff8b1b823fac8940aea40771d9ebea782 Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Fri, 19 Jul 2019 22:36:06 -0700 Subject: [PATCH 151/175] GUACAMOLE-847: Fix severe memory leak when using audio with RDP --- .../rdp/guac_rdpsnd/rdpsnd_service.c | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c b/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c index cc367c4f..45de442d 100644 --- a/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c +++ b/src/protocols/rdp/guac_rdpsnd/rdpsnd_service.c @@ -110,35 +110,36 @@ void guac_rdpsnd_process_receive(rdpSvcPlugin* plugin, * If next PDU is SNDWAVE (due to receiving WaveInfo PDU previously), * ignore the header and parse as a Wave PDU. */ - if (rdpsnd->next_pdu_is_wave) { + if (rdpsnd->next_pdu_is_wave) guac_rdpsnd_wave_handler(rdpsnd, input_stream, &header); - return; - } - + /* Dispatch message to standard handlers */ - switch (header.message_type) { + else { + switch (header.message_type) { - /* Server Audio Formats and Version PDU */ - case SNDC_FORMATS: - guac_rdpsnd_formats_handler(rdpsnd, input_stream, &header); - break; + /* Server Audio Formats and Version PDU */ + case SNDC_FORMATS: + guac_rdpsnd_formats_handler(rdpsnd, input_stream, &header); + break; - /* Training PDU */ - case SNDC_TRAINING: - guac_rdpsnd_training_handler(rdpsnd, input_stream, &header); - break; + /* Training PDU */ + case SNDC_TRAINING: + guac_rdpsnd_training_handler(rdpsnd, input_stream, &header); + break; - /* WaveInfo PDU */ - case SNDC_WAVE: - guac_rdpsnd_wave_info_handler(rdpsnd, input_stream, &header); - break; + /* WaveInfo PDU */ + case SNDC_WAVE: + guac_rdpsnd_wave_info_handler(rdpsnd, input_stream, &header); + break; - /* Close PDU */ - case SNDC_CLOSE: - guac_rdpsnd_close_handler(rdpsnd, input_stream, &header); - break; + /* Close PDU */ + case SNDC_CLOSE: + guac_rdpsnd_close_handler(rdpsnd, input_stream, &header); + break; + } } + Stream_Free(input_stream, TRUE); } From 26bb10a48673341f82743b5c595c2e9d5e7e79e5 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 23 Jul 2019 14:29:50 -0400 Subject: [PATCH 152/175] GUACAMOLE-422: Add empty handler and trace logging for nop instruction. --- src/libguac/user-handlers.c | 7 +++++++ src/libguac/user-handlers.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index eeed9bcd..f64fd299 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -51,6 +51,7 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = { {"put", __guac_handle_put}, {"audio", __guac_handle_audio}, {"argv", __guac_handle_argv}, + {"nop", __guac_handle_nop}, {NULL, NULL} }; @@ -588,6 +589,12 @@ int __guac_handle_put(guac_user* user, int argc, char** argv) { return 0; } +int __guac_handle_nop(guac_user* user, int argc, char** argv) { + guac_user_log(user, GUAC_LOG_TRACE, + "Received nop instruction"); + return 0; +} + int __guac_handle_disconnect(guac_user* user, int argc, char** argv) { guac_user_stop(user); return 0; diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index 17cfc0db..263c2928 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -177,6 +177,13 @@ __guac_instruction_handler __guac_handle_size; */ __guac_instruction_handler __guac_handle_disconnect; +/** + * Internal handler for the nop instruction. This handler will be called when + * the nop instruction is received, and will do nothing more than a TRACE level + * log of the instruction. + */ +__guac_instruction_handler __guac_handle_nop; + /** * Internal handler function that is called when the size instruction is * received during the handshake process. From a763d47bc7b12c109b25a0f9185a3fd63984c04e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 13:06:46 -0700 Subject: [PATCH 153/175] GUACAMOLE-629: Add support for sending "argv" instructions from server to client. --- src/libguac/guacamole/protocol.h | 25 +++++++++++++++++++++++++ src/libguac/protocol.c | 20 ++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 0b462b4c..fb8fbefe 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -932,6 +932,31 @@ int guac_protocol_send_size(guac_socket* socket, const guac_layer* layer, /* TEXT INSTRUCTIONS */ +/** + * Sends an argv instruction over the given guac_socket connection. + * + * If an error occurs sending the instruction, a non-zero value is + * returned, and guac_error is set appropriately. + * + * @param socket + * The guac_socket connection to use to send the connection parameter + * value. + * + * @param stream + * The stream to use to send the connection parameter value. + * + * @param mimetype + * The mimetype of the connection parameter value being sent. + * + * @param name + * The name of the connection parameter whose current value is being sent. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_argv(guac_socket* socket, guac_stream* stream, + const char* mimetype, const char* name); + /** * Sends a clipboard instruction over the given guac_socket connection. * diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 46a9858e..53b534de 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -125,6 +125,26 @@ int guac_protocol_send_args(guac_socket* socket, const char** args) { } +int guac_protocol_send_argv(guac_socket* socket, guac_stream* stream, + const char* mimetype, const char* name) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "4.argv,") + || __guac_socket_write_length_int(socket, stream->index) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, mimetype) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, name) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + int guac_protocol_send_arc(guac_socket* socket, const guac_layer* layer, int x, int y, int radius, double startAngle, double endAngle, int negative) { From c1b82503000659596e3b2700c1987d4f548c0ed9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 13:26:03 -0700 Subject: [PATCH 154/175] GUACAMOLE-629: Move constants for Guacamole protocol to dedicated "*-constants.h" header. --- src/libguac/Makefile.am | 1 + src/libguac/guacamole/protocol-constants.h | 44 ++++++++++++++++++++++ src/libguac/guacamole/protocol.h | 15 +------- 3 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/libguac/guacamole/protocol-constants.h diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index dc96a878..a6240802 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -54,6 +54,7 @@ libguacinc_HEADERS = \ guacamole/pool.h \ guacamole/pool-types.h \ guacamole/protocol.h \ + guacamole/protocol-constants.h \ guacamole/protocol-types.h \ guacamole/socket-constants.h \ guacamole/socket.h \ diff --git a/src/libguac/guacamole/protocol-constants.h b/src/libguac/guacamole/protocol-constants.h new file mode 100644 index 00000000..6ad7bf14 --- /dev/null +++ b/src/libguac/guacamole/protocol-constants.h @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_PROTOCOL_CONSTANTS_H +#define GUAC_PROTOCOL_CONSTANTS_H + +/** + * Constants related to the Guacamole protocol. + * + * @file protocol-constants.h + */ + +/** + * This defines the overall protocol version that this build of libguac + * supports. The protocol version is used to provide compatibility between + * potentially different versions of Guacamole server and clients. The + * version number is a MAJOR_MINOR_PATCH version that matches the versioning + * used throughout the components of the Guacamole project. This version + * will not necessarily increment with the other components, unless additional + * functionality is introduced that affects compatibility. + * + * This version is passed by the __guac_protocol_send_args() function from the + * server to the client during the client/server handshake. + */ +#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0" + +#endif + diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index fb8fbefe..2a5b138b 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -30,6 +30,7 @@ #include "layer-types.h" #include "object-types.h" +#include "protocol-constants.h" #include "protocol-types.h" #include "socket-types.h" #include "stream-types.h" @@ -38,20 +39,6 @@ #include #include -/** - * This defines the overall protocol version that this build of libguac - * supports. The protocol version is used to provide compatibility between - * potentially different versions of Guacamole server and clients. The - * version number is a MAJOR_MINOR_PATCH version that matches the versioning - * used throughout the components of the Guacamole project. This version - * will not necessarily increment with the other components, unless additional - * functionality is introduced that affects compatibility. - * - * This version is passed by the __guac_protocol_send_args() function from the - * server to the client during the client/server handshake. - */ -#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0" - /* CONTROL INSTRUCTIONS */ /** From c47aa0cea1a654152208272bb59cd0c94e3351c7 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 13:34:24 -0700 Subject: [PATCH 155/175] GUACAMOLE-629: Define constant for maximum blob size. --- src/libguac/encode-jpeg.c | 2 +- src/libguac/encode-png.c | 2 +- src/libguac/encode-webp.c | 2 +- src/libguac/guacamole/protocol-constants.h | 9 +++++++++ src/libguac/raw_encoder.c | 4 ++-- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libguac/encode-jpeg.c b/src/libguac/encode-jpeg.c index 1aeb23bb..8e145d86 100644 --- a/src/libguac/encode-jpeg.c +++ b/src/libguac/encode-jpeg.c @@ -59,7 +59,7 @@ typedef struct guac_jpeg_destination_mgr { /** * The output buffer. */ - unsigned char buffer[6048]; + unsigned char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; } guac_jpeg_destination_mgr; diff --git a/src/libguac/encode-png.c b/src/libguac/encode-png.c index 7d4b6c7f..20d2f5de 100644 --- a/src/libguac/encode-png.c +++ b/src/libguac/encode-png.c @@ -56,7 +56,7 @@ typedef struct guac_png_write_state { /** * Buffer of pending PNG data. */ - char buffer[6048]; + char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; /** * The number of bytes currently stored in the buffer. diff --git a/src/libguac/encode-webp.c b/src/libguac/encode-webp.c index 5c2237d6..43c5a00c 100644 --- a/src/libguac/encode-webp.c +++ b/src/libguac/encode-webp.c @@ -52,7 +52,7 @@ typedef struct guac_webp_stream_writer { /** * Buffer of pending WebP data. */ - char buffer[6048]; + char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH]; /** * The number of bytes currently stored in the buffer. diff --git a/src/libguac/guacamole/protocol-constants.h b/src/libguac/guacamole/protocol-constants.h index 6ad7bf14..afa67bb9 100644 --- a/src/libguac/guacamole/protocol-constants.h +++ b/src/libguac/guacamole/protocol-constants.h @@ -40,5 +40,14 @@ */ #define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0" +/** + * The maximum number of bytes that should be sent in any one blob instruction + * to ensure the instruction does not exceed the maximum allowed instruction + * size. + * + * @see GUAC_INSTRUCTION_MAX_LENGTH + */ +#define GUAC_PROTOCOL_BLOB_MAX_LENGTH 6048 + #endif diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c index 9086a37b..98024cd2 100644 --- a/src/libguac/raw_encoder.c +++ b/src/libguac/raw_encoder.c @@ -129,8 +129,8 @@ static void raw_encoder_flush_handler(guac_audio_stream* audio) { /* Determine size of blob to be written */ int chunk_size = remaining; - if (chunk_size > 6048) - chunk_size = 6048; + if (chunk_size > GUAC_PROTOCOL_BLOB_MAX_LENGTH) + chunk_size = GUAC_PROTOCOL_BLOB_MAX_LENGTH; /* Send audio data */ guac_protocol_send_blob(socket, stream, current, chunk_size); From c3a295e9b7c7d6001cdd4bb4ac4c625dade1a752 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 13:42:15 -0700 Subject: [PATCH 156/175] GUACAMOLE-629: Add convenience function for automatically splitting data across a series of blob instructions. --- src/libguac/guacamole/protocol.h | 31 +++++++++++++++++++++++++++++++ src/libguac/protocol.c | 27 +++++++++++++++++++++++++++ src/libguac/raw_encoder.c | 19 +------------------ 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 2a5b138b..f4e02880 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -435,6 +435,37 @@ int guac_protocol_send_pipe(guac_socket* socket, const guac_stream* stream, int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream, const void* data, int count); +/** + * Sends a series of blob instructions, splitting the given data across the + * number of instructions required to ensure the size of each blob does not + * exceed GUAC_PROTOCOL_BLOB_MAX_LENGTH. If the size of data provided is zero, + * no blob instructions are sent. + * + * If an error occurs sending any blob instruction, a non-zero value is + * returned, guac_error is set appropriately, and no further blobs are sent. + * + * @see GUAC_PROTOCOL_BLOB_MAX_LENGTH + * + * @param socket + * The guac_socket connection to use to send the blob instructions. + * + * @param stream + * The stream to associate with each blob sent. + * + * @param data + * The data which should be sent using the required number of blob + * instructions. + * + * @param count + * The number of bytes within the given buffer of data that must be + * written. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_blobs(guac_socket* socket, const guac_stream* stream, + const void* data, int count); + /** * Sends an end instruction over the given guac_socket connection. * diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index 53b534de..ff73a558 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -215,6 +215,33 @@ int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream, } +int guac_protocol_send_blobs(guac_socket* socket, const guac_stream* stream, + const void* data, int count) { + + int ret_val = 0; + + /* Send blob instructions while data remains and instructions are being + * sent successfully */ + while (count > 0 && ret_val == 0) { + + /* Limit blob size to maximum allowed */ + int blob_size = count; + if (blob_size > GUAC_PROTOCOL_BLOB_MAX_LENGTH) + blob_size = GUAC_PROTOCOL_BLOB_MAX_LENGTH; + + /* Send next blob of data */ + ret_val = guac_protocol_send_blob(socket, stream, data, blob_size); + + /* Advance to next blob */ + data = (const char*) data + blob_size; + count -= blob_size; + + } + + return ret_val; + +} + int guac_protocol_send_body(guac_socket* socket, const guac_object* object, const guac_stream* stream, const char* mimetype, const char* name) { diff --git a/src/libguac/raw_encoder.c b/src/libguac/raw_encoder.c index 98024cd2..888bde79 100644 --- a/src/libguac/raw_encoder.c +++ b/src/libguac/raw_encoder.c @@ -121,25 +121,8 @@ static void raw_encoder_flush_handler(guac_audio_stream* audio) { guac_socket* socket = audio->client->socket; guac_stream* stream = audio->stream; - unsigned char* current = state->buffer; - int remaining = state->written; - /* Flush all data in buffer as blobs */ - while (remaining > 0) { - - /* Determine size of blob to be written */ - int chunk_size = remaining; - if (chunk_size > GUAC_PROTOCOL_BLOB_MAX_LENGTH) - chunk_size = GUAC_PROTOCOL_BLOB_MAX_LENGTH; - - /* Send audio data */ - guac_protocol_send_blob(socket, stream, current, chunk_size); - - /* Advance to next blob */ - current += chunk_size; - remaining -= chunk_size; - - } + guac_protocol_send_blobs(socket, stream, state->buffer, state->written); /* All data has been flushed */ state->written = 0; From ca073db5b6945ac2de946a5b80179ce2d57550af Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 13:51:10 -0700 Subject: [PATCH 157/175] GUACAMOLE-629: Add convenience functions for immediately sending the current value of a connection parameter. --- src/libguac/client.c | 20 ++++++++++++++++++++ src/libguac/guacamole/client.h | 27 +++++++++++++++++++++++++++ src/libguac/guacamole/user.h | 26 ++++++++++++++++++++++++++ src/libguac/user.c | 20 ++++++++++++++++++++ 4 files changed, 93 insertions(+) diff --git a/src/libguac/client.c b/src/libguac/client.c index 80eb4ea7..64404534 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -516,6 +516,26 @@ int guac_client_get_processing_lag(guac_client* client) { } +void guac_client_stream_argv(guac_client* client, guac_socket* socket, + const char* mimetype, const char* name, const char* value) { + + /* Allocate new stream for argument value */ + guac_stream* stream = guac_client_alloc_stream(client); + + /* Declare stream as containing connection parameter data */ + guac_protocol_send_argv(socket, stream, mimetype, name); + + /* Write parameter data */ + guac_protocol_send_blobs(socket, stream, value, strlen(value)); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_client_free_stream(client, stream); + +} + void guac_client_stream_png(guac_client* client, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface) { diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index 2d01573c..88d1f416 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -548,6 +548,33 @@ int guac_client_load_plugin(guac_client* client, const char* protocol); */ int guac_client_get_processing_lag(guac_client* client); +/** + * Streams the given connection parameter value over an argument value stream + * ("argv" instruction), exposing the current value of the named connection + * parameter to all users of the given client. The argument value stream will + * be automatically allocated and freed. + * + * @param client + * The Guacamole client for which the argument value stream should be + * allocated. + * + * @param socket + * The socket over which instructions associated with the argument value + * stream should be sent. + * + * @param mimetype + * The mimetype of the data within the connection parameter value being + * sent. + * + * @param name + * The name of the connection parameter being sent. + * + * @param value + * The current value of the connection parameter being sent. + */ +void guac_client_stream_argv(guac_client* client, guac_socket* socket, + const char* mimetype, const char* name, const char* value); + /** * Streams the image data of the given surface over an image stream ("img" * instruction) as PNG-encoded data. The image stream will be automatically diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index f920c325..702160fa 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -676,6 +676,32 @@ guac_object* guac_user_alloc_object(guac_user* user); */ void guac_user_free_object(guac_user* user, guac_object* object); +/** + * Streams the given connection parameter value over an argument value stream + * ("argv" instruction), exposing the current value of the named connection + * parameter to the given user. The argument value stream will be automatically + * allocated and freed. + * + * @param user + * The Guacamole user who should receive the connection parameter value. + * + * @param socket + * The socket over which instructions associated with the argument value + * stream should be sent. + * + * @param mimetype + * The mimetype of the data within the connection parameter value being + * sent. + * + * @param name + * The name of the connection parameter being sent. + * + * @param value + * The current value of the connection parameter being sent. + */ +void guac_user_stream_argv(guac_user* user, guac_socket* socket, + const char* mimetype, const char* name, const char* value); + /** * Streams the image data of the given surface over an image stream ("img" * instruction) as PNG-encoded data. The image stream will be automatically diff --git a/src/libguac/user.c b/src/libguac/user.c index 9145b275..aaa51bb9 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -229,6 +229,26 @@ void guac_user_log(guac_user* user, guac_client_log_level level, } +void guac_user_stream_argv(guac_user* user, guac_socket* socket, + const char* mimetype, const char* name, const char* value) { + + /* Allocate new stream for argument value */ + guac_stream* stream = guac_user_alloc_stream(user); + + /* Declare stream as containing connection parameter data */ + guac_protocol_send_argv(socket, stream, mimetype, name); + + /* Write parameter data */ + guac_protocol_send_blobs(socket, stream, value, strlen(value)); + + /* Terminate stream */ + guac_protocol_send_end(socket, stream); + + /* Free allocated stream */ + guac_user_free_stream(user, stream); + +} + void guac_user_stream_png(guac_user* user, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface) { From e6835795f0311f1e7f35ca4534811de4a316f10d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 3 Aug 2019 11:28:38 -0700 Subject: [PATCH 158/175] GUACAMOLE-623: Add support for setting terminal font for Kubernetes via "argv". --- src/protocols/kubernetes/Makefile.am | 2 + src/protocols/kubernetes/argv.c | 195 +++++++++++++++++++++++++++ src/protocols/kubernetes/argv.h | 41 ++++++ src/protocols/kubernetes/user.c | 4 + 4 files changed, 242 insertions(+) create mode 100644 src/protocols/kubernetes/argv.c create mode 100644 src/protocols/kubernetes/argv.h diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am index b100c176..f22e91f8 100644 --- a/src/protocols/kubernetes/Makefile.am +++ b/src/protocols/kubernetes/Makefile.am @@ -29,6 +29,7 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-kubernetes.la libguac_client_kubernetes_la_SOURCES = \ + argv.c \ client.c \ clipboard.c \ input.c \ @@ -41,6 +42,7 @@ libguac_client_kubernetes_la_SOURCES = \ user.c noinst_HEADERS = \ + argv.h \ client.h \ clipboard.h \ input.h \ diff --git a/src/protocols/kubernetes/argv.c b/src/protocols/kubernetes/argv.c new file mode 100644 index 00000000..0bff3149 --- /dev/null +++ b/src/protocols/kubernetes/argv.c @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "argv.h" +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include +#include + +/** + * All Kubernetes connection settings which may be updated by unprivileged + * users through "argv" streams. + */ +typedef enum guac_kubernetes_argv_setting { + + /** + * The color scheme of the terminal. + */ + GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME, + + /** + * The name of the font family used by the terminal. + */ + GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME, + + /** + * The size of the font used by the terminal, in points. + */ + GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE + +} guac_kubernetes_argv_setting; + +/** + * The value or current status of a connection parameter received over an + * "argv" stream. + */ +typedef struct guac_kubernetes_argv { + + /** + * The specific setting being updated. + */ + guac_kubernetes_argv_setting setting; + + /** + * Buffer space for containing the received argument value. + */ + char buffer[GUAC_KUBERNETES_ARGV_MAX_LENGTH]; + + /** + * The number of bytes received so far. + */ + int length; + +} guac_kubernetes_argv; + +/** + * Handler for "blob" instructions which appends the data from received blobs + * to the end of the in-progress argument value buffer. + * + * @see guac_user_blob_handler + */ +static int guac_kubernetes_argv_blob_handler(guac_user* user, + guac_stream* stream, void* data, int length) { + + guac_kubernetes_argv* argv = (guac_kubernetes_argv*) stream->data; + + /* Calculate buffer size remaining, including space for null terminator, + * adjusting received length accordingly */ + int remaining = sizeof(argv->buffer) - argv->length - 1; + if (length > remaining) + length = remaining; + + /* Append received data to end of buffer */ + memcpy(argv->buffer + argv->length, data, length); + argv->length += length; + + return 0; + +} + +/** + * Handler for "end" instructions which applies the changes specified by the + * argument value buffer associated with the stream. + * + * @see guac_user_end_handler + */ +static int guac_kubernetes_argv_end_handler(guac_user* user, + guac_stream* stream) { + + int size; + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data; + guac_terminal* terminal = kubernetes_client->term; + + /* Append null terminator to value */ + guac_kubernetes_argv* argv = (guac_kubernetes_argv*) stream->data; + argv->buffer[argv->length] = '\0'; + + /* Apply changes to chosen setting */ + switch (argv->setting) { + + /* Update color scheme */ + case GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME: + guac_terminal_apply_color_scheme(terminal, argv->buffer); + break; + + /* Update font name */ + case GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME: + guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + break; + + /* Update font size */ + case GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE: + + /* Update only if font size is sane */ + size = atoi(argv->buffer); + if (size > 0) { + guac_terminal_apply_font(terminal, NULL, size, + kubernetes_client->settings->resolution); + } + + break; + + } + + /* Update Kubernetes terminal size */ + guac_kubernetes_resize(client, terminal->term_height, + terminal->term_width); + + free(argv); + return 0; + +} + +int guac_kubernetes_argv_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + guac_kubernetes_argv_setting setting; + + /* Allow users to update the color scheme and font details */ + if (strcmp(name, "color-scheme") == 0) + setting = GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME; + else if (strcmp(name, "font-name") == 0) + setting = GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME; + else if (strcmp(name, "font-size") == 0) + setting = GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE; + + /* No other connection parameters may be updated */ + else { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + guac_kubernetes_argv* argv = malloc(sizeof(guac_kubernetes_argv)); + argv->setting = setting; + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_kubernetes_argv_blob_handler; + stream->end_handler = guac_kubernetes_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/kubernetes/argv.h b/src/protocols/kubernetes/argv.h new file mode 100644 index 00000000..da9e11b7 --- /dev/null +++ b/src/protocols/kubernetes/argv.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_KUBERNETES_ARGV_H +#define GUAC_KUBERNETES_ARGV_H + +#include "config.h" + +#include + +/** + * The maximum number of bytes to allow for any argument value received via an + * argv stream, including null terminator. + */ +#define GUAC_KUBERNETES_ARGV_MAX_LENGTH 16384 + +/** + * Handles an incoming stream from a Guacamole "argv" instruction, updating the + * given connection parameter if that parameter is allowed to be updated. + */ +guac_user_argv_handler guac_kubernetes_argv_handler; + +#endif + diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c index f90260e7..a2fdd200 100644 --- a/src/protocols/kubernetes/user.c +++ b/src/protocols/kubernetes/user.c @@ -17,6 +17,7 @@ * under the License. */ +#include "argv.h" #include "clipboard.h" #include "common/cursor.h" #include "input.h" @@ -87,6 +88,9 @@ int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { /* STDIN redirection */ user->pipe_handler = guac_kubernetes_pipe_handler; + /* Updates to connection parameters */ + user->argv_handler = guac_kubernetes_argv_handler; + /* Display size change events */ user->size_handler = guac_kubernetes_user_size_handler; From 0c7898c55a44ed70e00944defec516eaba9b478a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 30 Jul 2019 16:19:18 -0700 Subject: [PATCH 159/175] GUACAMOLE-630: Expose terminal font/color configuration changes to connected clients. --- src/protocols/kubernetes/argv.c | 6 ++++++ src/protocols/ssh/argv.c | 6 ++++++ src/protocols/telnet/argv.c | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/src/protocols/kubernetes/argv.c b/src/protocols/kubernetes/argv.c index 0bff3149..e46bb2fd 100644 --- a/src/protocols/kubernetes/argv.c +++ b/src/protocols/kubernetes/argv.c @@ -125,11 +125,15 @@ static int guac_kubernetes_argv_end_handler(guac_user* user, /* Update color scheme */ case GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME: guac_terminal_apply_color_scheme(terminal, argv->buffer); + guac_client_stream_argv(client, client->socket, "text/plain", + "color-scheme", argv->buffer); break; /* Update font name */ case GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME: guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-name", argv->buffer); break; /* Update font size */ @@ -140,6 +144,8 @@ static int guac_kubernetes_argv_end_handler(guac_user* user, if (size > 0) { guac_terminal_apply_font(terminal, NULL, size, kubernetes_client->settings->resolution); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-size", argv->buffer); } break; diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index 8db2b242..0a4e6705 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -126,11 +126,15 @@ static int guac_ssh_argv_end_handler(guac_user* user, /* Update color scheme */ case GUAC_SSH_ARGV_SETTING_COLOR_SCHEME: guac_terminal_apply_color_scheme(terminal, argv->buffer); + guac_client_stream_argv(client, client->socket, "text/plain", + "color-scheme", argv->buffer); break; /* Update font name */ case GUAC_SSH_ARGV_SETTING_FONT_NAME: guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-name", argv->buffer); break; /* Update font size */ @@ -141,6 +145,8 @@ static int guac_ssh_argv_end_handler(guac_user* user, if (size > 0) { guac_terminal_apply_font(terminal, NULL, size, ssh_client->settings->resolution); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-size", argv->buffer); } break; diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 450c7f35..5edf060a 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -125,11 +125,15 @@ static int guac_telnet_argv_end_handler(guac_user* user, /* Update color scheme */ case GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME: guac_terminal_apply_color_scheme(terminal, argv->buffer); + guac_client_stream_argv(client, client->socket, "text/plain", + "color-scheme", argv->buffer); break; /* Update font name */ case GUAC_TELNET_ARGV_SETTING_FONT_NAME: guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-name", argv->buffer); break; /* Update font size */ @@ -140,6 +144,8 @@ static int guac_telnet_argv_end_handler(guac_user* user, if (size > 0) { guac_terminal_apply_font(terminal, NULL, size, telnet_client->settings->resolution); + guac_client_stream_argv(client, client->socket, "text/plain", + "font-size", argv->buffer); } break; From b5191caddc188f151e8fc468261284a7bc7d1305 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 4 Aug 2019 11:37:42 -0700 Subject: [PATCH 160/175] GUACAMOLE-630: Accept pre-defined color schemes in all cases, not just during terminal creation. --- src/terminal/color-scheme.c | 17 +++++++++++++++++ src/terminal/terminal.c | 17 ----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/terminal/color-scheme.c b/src/terminal/color-scheme.c index f4300196..7c8673ac 100644 --- a/src/terminal/color-scheme.c +++ b/src/terminal/color-scheme.c @@ -196,6 +196,23 @@ void guac_terminal_parse_color_scheme(guac_client* client, guac_terminal_color* background, guac_terminal_color (*palette)[256]) { + /* Special cases. */ + if (color_scheme == NULL || color_scheme[0] == '\0') { + /* guac_terminal_parse_color_scheme defaults to gray-black */ + } + else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GRAY_BLACK) == 0) { + color_scheme = "foreground:color7;background:color0"; + } + else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_BLACK_WHITE) == 0) { + color_scheme = "foreground:color0;background:color15"; + } + else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GREEN_BLACK) == 0) { + color_scheme = "foreground:color2;background:color0"; + } + else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_WHITE_BLACK) == 0) { + color_scheme = "foreground:color15;background:color0"; + } + /* Set default gray-black color scheme and initial palette. */ *foreground = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_GRAY]; *background = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_BLACK]; diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index cf70f92d..e502586a 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -327,23 +327,6 @@ guac_terminal* guac_terminal_create(guac_client* client, guac_terminal_color (*default_palette)[256] = (guac_terminal_color(*)[256]) malloc(sizeof(guac_terminal_color[256])); - /* Special cases. */ - if (color_scheme == NULL || color_scheme[0] == '\0') { - /* guac_terminal_parse_color_scheme defaults to gray-black */ - } - else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GRAY_BLACK) == 0) { - color_scheme = "foreground:color7;background:color0"; - } - else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_BLACK_WHITE) == 0) { - color_scheme = "foreground:color0;background:color15"; - } - else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GREEN_BLACK) == 0) { - color_scheme = "foreground:color2;background:color0"; - } - else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_WHITE_BLACK) == 0) { - color_scheme = "foreground:color15;background:color0"; - } - guac_terminal_parse_color_scheme(client, color_scheme, &default_char.attributes.foreground, &default_char.attributes.background, From 0516d599cffc04f0216f8fe444fad2208b2a2877 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 4 Aug 2019 11:45:29 -0700 Subject: [PATCH 161/175] GUACAMOLE-630: Disallow NULL color schemes. A color scheme string should always be provided, even if blank. Disallowing NULL allows assumptions to be made which simplifies the logic surrounding persisting provided configuration values. --- src/terminal/color-scheme.c | 2 +- src/terminal/terminal/terminal.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/terminal/color-scheme.c b/src/terminal/color-scheme.c index 7c8673ac..5df181fa 100644 --- a/src/terminal/color-scheme.c +++ b/src/terminal/color-scheme.c @@ -197,7 +197,7 @@ void guac_terminal_parse_color_scheme(guac_client* client, guac_terminal_color (*palette)[256]) { /* Special cases. */ - if (color_scheme == NULL || color_scheme[0] == '\0') { + if (color_scheme[0] == '\0') { /* guac_terminal_parse_color_scheme defaults to gray-black */ } else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GRAY_BLACK) == 0) { diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index ac1217eb..95a78d8b 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -561,8 +561,8 @@ struct guac_terminal { * * @param color_scheme * The name of the color scheme to use. This string must be one of the - * names defined by the GUAC_TERMINAL_SCHEME_* constants. If blank or NULL, - * the default scheme of GUAC_TERMINAL_SCHEME_GRAY_BLACK will be used. If + * names defined by the GUAC_TERMINAL_SCHEME_* constants. If blank, the + * default scheme of GUAC_TERMINAL_SCHEME_GRAY_BLACK will be used. If * invalid, a warning will be logged, and the terminal will fall back on * GUAC_TERMINAL_SCHEME_GRAY_BLACK. * From 4dabea37af81cc5a7152237c3db670df3a65566f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 4 Aug 2019 11:55:45 -0700 Subject: [PATCH 162/175] GUACAMOLE-630: Allow guac_terminal_parse_color_scheme() to dictate color scheme format. --- src/terminal/terminal/color-scheme.h | 12 +++++++++--- src/terminal/terminal/terminal.h | 7 ++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/terminal/terminal/color-scheme.h b/src/terminal/terminal/color-scheme.h index 962d820d..5417ce1c 100644 --- a/src/terminal/terminal/color-scheme.h +++ b/src/terminal/terminal/color-scheme.h @@ -72,9 +72,15 @@ * The client that the terminal is connected to. * * @param color_scheme - * A semicolon-separated list of name-value pairs, i.e. - * ": [; : [; ...]]". - * For example, "color2: rgb:cc/33/22; background: color5". + * The name of a pre-defined color scheme (one of the + * names defined by the GUAC_TERMINAL_SCHEME_* constants), or + * semicolon-separated list of name-value pairs, i.e. ": [; + * : [; ...]]". For example, "color2: rgb:cc/33/22; + * background: color5". + * + * If blank, the default scheme of GUAC_TERMINAL_SCHEME_GRAY_BLACK will be + * used. If invalid, a warning will be logged, and + * GUAC_TERMINAL_SCHEME_GRAY_BLACK will be used. * * @param[out] foreground * Parsed foreground color. diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 95a78d8b..c2fd456b 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -560,11 +560,8 @@ struct guac_terminal { * The height of the terminal, in pixels. * * @param color_scheme - * The name of the color scheme to use. This string must be one of the - * names defined by the GUAC_TERMINAL_SCHEME_* constants. If blank, the - * default scheme of GUAC_TERMINAL_SCHEME_GRAY_BLACK will be used. If - * invalid, a warning will be logged, and the terminal will fall back on - * GUAC_TERMINAL_SCHEME_GRAY_BLACK. + * The name of the color scheme to use. This string must be in the format + * accepted by guac_terminal_parse_color_scheme(). * * @param backspace * The integer ASCII code to send when backspace is pressed in From ccfcfb116d6dc9cb8881f04d5072796c91619d86 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 4 Aug 2019 12:44:43 -0700 Subject: [PATCH 163/175] GUACAMOLE-630: Persist details of color scheme and font changes. --- src/terminal/terminal.c | 33 ++++++++++++++++++++++++++++++++ src/terminal/terminal/terminal.h | 19 ++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index e502586a..05bf7209 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -343,6 +343,11 @@ guac_terminal* guac_terminal_create(guac_client* client, term->upload_path_handler = NULL; term->file_download_handler = NULL; + /* Copy initially-provided color scheme and font details */ + term->color_scheme = strdup(color_scheme); + term->font_name = strdup(font_name); + term->font_size = font_size; + /* Set size of available screen area */ term->outer_width = width; term->outer_height = height; @@ -511,6 +516,10 @@ void guac_terminal_free(guac_terminal* term) { /* Free scrollbar */ guac_terminal_scrollbar_free(term->scrollbar); + /* Free copies of font and color scheme information */ + free((char*) term->color_scheme); + free((char*) term->font_name); + /* Free the terminal itself */ free(term); @@ -1955,6 +1964,16 @@ void guac_terminal_apply_color_scheme(guac_terminal* terminal, terminal->term_height - 1, terminal->term_width - 1); + /* Acquire exclusive access to terminal */ + guac_terminal_lock(terminal); + + /* Update stored copy of color scheme */ + free((char*) terminal->color_scheme); + terminal->color_scheme = strdup(color_scheme); + + /* Release terminal */ + guac_terminal_unlock(terminal); + guac_terminal_notify(terminal); } @@ -1979,6 +1998,20 @@ void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, terminal->term_height - 1, terminal->term_width - 1); + /* Acquire exclusive access to terminal */ + guac_terminal_lock(terminal); + + /* Update stored copy of font name, if changed */ + if (font_name != NULL) + terminal->font_name = strdup(font_name); + + /* Update stored copy of font size, if changed */ + if (font_size != -1) + terminal->font_size = font_size; + + /* Release terminal */ + guac_terminal_unlock(terminal); + guac_terminal_notify(terminal); } diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index c2fd456b..e7746cba 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -506,6 +506,25 @@ struct guac_terminal { */ guac_common_clipboard* clipboard; + /** + * The name of the font to use when rendering glyphs, as requested at + * creation time or via guac_terminal_apply_font(). + */ + const char* font_name; + + /** + * The size of each glyph, in points, as requested at creation time or via + * guac_terminal_apply_font(). + */ + int font_size; + + /** + * The name of the color scheme to use, as requested at creation time or + * via guac_terminal_apply_color_scheme(). This string must be in the + * format accepted by guac_terminal_parse_color_scheme(). + */ + const char* color_scheme; + /** * ASCII character to send when backspace is pressed. */ From f56df8b8be9ca0f8afd4bec74f9040e03b4eea95 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 4 Aug 2019 12:52:00 -0700 Subject: [PATCH 164/175] GUACAMOLE-630: Automatically send current color scheme and font to users joining SSH, telnet, and Kubernetes connections. --- src/protocols/kubernetes/argv.c | 23 +++++++++++++++++++++++ src/protocols/kubernetes/argv.h | 21 +++++++++++++++++++++ src/protocols/kubernetes/kubernetes.c | 6 ++++++ src/protocols/kubernetes/user.c | 1 + src/protocols/ssh/argv.c | 23 +++++++++++++++++++++++ src/protocols/ssh/argv.h | 20 ++++++++++++++++++++ src/protocols/ssh/ssh.c | 4 ++++ src/protocols/ssh/user.c | 1 + src/protocols/telnet/argv.c | 23 +++++++++++++++++++++++ src/protocols/telnet/argv.h | 20 ++++++++++++++++++++ src/protocols/telnet/telnet.c | 6 ++++++ src/protocols/telnet/user.c | 1 + 12 files changed, 149 insertions(+) diff --git a/src/protocols/kubernetes/argv.c b/src/protocols/kubernetes/argv.c index e46bb2fd..c37595de 100644 --- a/src/protocols/kubernetes/argv.c +++ b/src/protocols/kubernetes/argv.c @@ -199,3 +199,26 @@ int guac_kubernetes_argv_handler(guac_user* user, guac_stream* stream, } +void* guac_kubernetes_send_current_argv(guac_user* user, void* data) { + + guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) data; + guac_terminal* terminal = kubernetes_client->term; + + /* Send current color scheme */ + guac_user_stream_argv(user, user->socket, "text/plain", "color-scheme", + terminal->color_scheme); + + /* Send current font name */ + guac_user_stream_argv(user, user->socket, "text/plain", "font-name", + terminal->font_name); + + /* Send current font size */ + char font_size[64]; + sprintf(font_size, "%i", terminal->font_size); + guac_user_stream_argv(user, user->socket, "text/plain", "font-size", + font_size); + + return NULL; + +} + diff --git a/src/protocols/kubernetes/argv.h b/src/protocols/kubernetes/argv.h index da9e11b7..1a23df13 100644 --- a/src/protocols/kubernetes/argv.h +++ b/src/protocols/kubernetes/argv.h @@ -37,5 +37,26 @@ */ guac_user_argv_handler guac_kubernetes_argv_handler; +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the given user. Note that the user + * receiving these values will not necessarily be able to set new values + * themselves if their connection is read-only. This function is provided for + * convenience, as it is can be used as the callback for + * guac_client_foreach_user() or guac_client_for_owner(). + * + * @param user + * The user that should receive the values of all non-sensitive parameters + * which may be set while the connection is running. + * + * @param data + * The guac_kubernetes_client instance associated with the current + * connection. + * + * @return + * Always NULL. + */ +void* guac_kubernetes_send_current_argv(guac_user* user, void* data); + #endif diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 7c22c420..bd84f6ce 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -18,6 +18,8 @@ */ #include "config.h" + +#include "argv.h" #include "client.h" #include "common/recording.h" #include "io.h" @@ -249,6 +251,10 @@ void* guac_kubernetes_client_thread(void* data) { goto fail; } + /* Send current values of exposed arguments to owner only */ + guac_client_for_owner(client, guac_kubernetes_send_current_argv, + kubernetes_client); + /* Set up typescript, if requested */ if (settings->typescript_path != NULL) { guac_terminal_create_typescript(kubernetes_client->term, diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c index a2fdd200..e7f6d8f9 100644 --- a/src/protocols/kubernetes/user.c +++ b/src/protocols/kubernetes/user.c @@ -74,6 +74,7 @@ int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { /* If not owner, synchronize with current display */ else { guac_terminal_dup(kubernetes_client->term, user, user->socket); + guac_kubernetes_send_current_argv(user, kubernetes_client); guac_socket_flush(user->socket); } diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index 0a4e6705..d9ce1e75 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -204,3 +204,26 @@ int guac_ssh_argv_handler(guac_user* user, guac_stream* stream, } +void* guac_ssh_send_current_argv(guac_user* user, void* data) { + + guac_ssh_client* ssh_client = (guac_ssh_client*) data; + guac_terminal* terminal = ssh_client->term; + + /* Send current color scheme */ + guac_user_stream_argv(user, user->socket, "text/plain", "color-scheme", + terminal->color_scheme); + + /* Send current font name */ + guac_user_stream_argv(user, user->socket, "text/plain", "font-name", + terminal->font_name); + + /* Send current font size */ + char font_size[64]; + sprintf(font_size, "%i", terminal->font_size); + guac_user_stream_argv(user, user->socket, "text/plain", "font-size", + font_size); + + return NULL; + +} + diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h index 88cf8e97..c7b8559b 100644 --- a/src/protocols/ssh/argv.h +++ b/src/protocols/ssh/argv.h @@ -37,5 +37,25 @@ */ guac_user_argv_handler guac_ssh_argv_handler; +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the given user. Note that the user + * receiving these values will not necessarily be able to set new values + * themselves if their connection is read-only. This function is provided for + * convenience, as it is can be used as the callback for + * guac_client_foreach_user() or guac_client_for_owner(). + * + * @param user + * The user that should receive the values of all non-sensitive parameters + * which may be set while the connection is running. + * + * @param data + * The guac_ssh_client instance associated with the current connection. + * + * @return + * Always NULL. + */ +void* guac_ssh_send_current_argv(guac_user* user, void* data); + #endif diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 8a18cf4d..456c2140 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -19,6 +19,7 @@ #include "config.h" +#include "argv.h" #include "common/recording.h" #include "common-ssh/sftp.h" #include "common-ssh/ssh.h" @@ -233,6 +234,9 @@ void* ssh_client_thread(void* data) { return NULL; } + /* Send current values of exposed arguments to owner only */ + guac_client_for_owner(client, guac_ssh_send_current_argv, ssh_client); + /* Set up typescript, if requested */ if (settings->typescript_path != NULL) { guac_terminal_create_typescript(ssh_client->term, diff --git a/src/protocols/ssh/user.c b/src/protocols/ssh/user.c index 97ed87c6..ccf2447c 100644 --- a/src/protocols/ssh/user.c +++ b/src/protocols/ssh/user.c @@ -74,6 +74,7 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) { /* If not owner, synchronize with current display */ else { guac_terminal_dup(ssh_client->term, user, user->socket); + guac_ssh_send_current_argv(user, ssh_client); guac_socket_flush(user->socket); } diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 5edf060a..37564a60 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -200,3 +200,26 @@ int guac_telnet_argv_handler(guac_user* user, guac_stream* stream, } +void* guac_telnet_send_current_argv(guac_user* user, void* data) { + + guac_telnet_client* telnet_client = (guac_telnet_client*) data; + guac_terminal* terminal = telnet_client->term; + + /* Send current color scheme */ + guac_user_stream_argv(user, user->socket, "text/plain", "color-scheme", + terminal->color_scheme); + + /* Send current font name */ + guac_user_stream_argv(user, user->socket, "text/plain", "font-name", + terminal->font_name); + + /* Send current font size */ + char font_size[64]; + sprintf(font_size, "%i", terminal->font_size); + guac_user_stream_argv(user, user->socket, "text/plain", "font-size", + font_size); + + return NULL; + +} + diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h index aa13dda9..4bbf8696 100644 --- a/src/protocols/telnet/argv.h +++ b/src/protocols/telnet/argv.h @@ -37,5 +37,25 @@ */ guac_user_argv_handler guac_telnet_argv_handler; +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the given user. Note that the user + * receiving these values will not necessarily be able to set new values + * themselves if their connection is read-only. This function is provided for + * convenience, as it is can be used as the callback for + * guac_client_foreach_user() or guac_client_for_owner(). + * + * @param user + * The user that should receive the values of all non-sensitive parameters + * which may be set while the connection is running. + * + * @param data + * The guac_telnet_client instance associated with the current connection. + * + * @return + * Always NULL. + */ +void* guac_telnet_send_current_argv(guac_user* user, void* data); + #endif diff --git a/src/protocols/telnet/telnet.c b/src/protocols/telnet/telnet.c index 17abd681..e100fae9 100644 --- a/src/protocols/telnet/telnet.c +++ b/src/protocols/telnet/telnet.c @@ -18,6 +18,8 @@ */ #include "config.h" + +#include "argv.h" #include "common/recording.h" #include "telnet.h" #include "terminal/terminal.h" @@ -580,6 +582,10 @@ void* guac_telnet_client_thread(void* data) { return NULL; } + /* Send current values of exposed arguments to owner only */ + guac_client_for_owner(client, guac_telnet_send_current_argv, + telnet_client); + /* Set up typescript, if requested */ if (settings->typescript_path != NULL) { guac_terminal_create_typescript(telnet_client->term, diff --git a/src/protocols/telnet/user.c b/src/protocols/telnet/user.c index 44ef9b5e..08cfc6fe 100644 --- a/src/protocols/telnet/user.c +++ b/src/protocols/telnet/user.c @@ -73,6 +73,7 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) { /* If not owner, synchronize with current display */ else { guac_terminal_dup(telnet_client->term, user, user->socket); + guac_telnet_send_current_argv(user, telnet_client); guac_socket_flush(user->socket); } From 43269920dbe429542bd8a4ba9b37401da2839876 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 7 Aug 2019 20:16:01 -0700 Subject: [PATCH 165/175] GUACAMOLE-630: Clarify usage of argv-related guac_user_callback implementations. --- src/protocols/kubernetes/argv.h | 5 ++--- src/protocols/ssh/argv.h | 5 ++--- src/protocols/telnet/argv.h | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/protocols/kubernetes/argv.h b/src/protocols/kubernetes/argv.h index 1a23df13..2fc6c1ba 100644 --- a/src/protocols/kubernetes/argv.h +++ b/src/protocols/kubernetes/argv.h @@ -41,9 +41,8 @@ guac_user_argv_handler guac_kubernetes_argv_handler; * Sends the current values of all non-sensitive parameters which may be set * while the connection is running to the given user. Note that the user * receiving these values will not necessarily be able to set new values - * themselves if their connection is read-only. This function is provided for - * convenience, as it is can be used as the callback for - * guac_client_foreach_user() or guac_client_for_owner(). + * themselves if their connection is read-only. This function can be used as + * the callback for guac_client_foreach_user() and guac_client_for_owner() * * @param user * The user that should receive the values of all non-sensitive parameters diff --git a/src/protocols/ssh/argv.h b/src/protocols/ssh/argv.h index c7b8559b..186e0b5f 100644 --- a/src/protocols/ssh/argv.h +++ b/src/protocols/ssh/argv.h @@ -41,9 +41,8 @@ guac_user_argv_handler guac_ssh_argv_handler; * Sends the current values of all non-sensitive parameters which may be set * while the connection is running to the given user. Note that the user * receiving these values will not necessarily be able to set new values - * themselves if their connection is read-only. This function is provided for - * convenience, as it is can be used as the callback for - * guac_client_foreach_user() or guac_client_for_owner(). + * themselves if their connection is read-only. This function can be used as + * the callback for guac_client_foreach_user() and guac_client_for_owner() * * @param user * The user that should receive the values of all non-sensitive parameters diff --git a/src/protocols/telnet/argv.h b/src/protocols/telnet/argv.h index 4bbf8696..e362e14a 100644 --- a/src/protocols/telnet/argv.h +++ b/src/protocols/telnet/argv.h @@ -41,9 +41,8 @@ guac_user_argv_handler guac_telnet_argv_handler; * Sends the current values of all non-sensitive parameters which may be set * while the connection is running to the given user. Note that the user * receiving these values will not necessarily be able to set new values - * themselves if their connection is read-only. This function is provided for - * convenience, as it is can be used as the callback for - * guac_client_foreach_user() or guac_client_for_owner(). + * themselves if their connection is read-only. This function can be used as + * the callback for guac_client_foreach_user() and guac_client_for_owner() * * @param user * The user that should receive the values of all non-sensitive parameters From 51ae8a41a138ff7a2a5b7e81b9647b7cd49ebdab Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 2 Aug 2019 23:14:17 -0400 Subject: [PATCH 166/175] GUACAMOLE-514: Add guacd support for extended VNC credentials. --- src/protocols/vnc/auth.c | 29 +++++++++++++++++++ src/protocols/vnc/auth.h | 16 ++++++++++- src/protocols/vnc/settings.c | 56 ++++++++++++++++++++++++++++++++++++ src/protocols/vnc/settings.h | 26 +++++++++++++++++ src/protocols/vnc/vnc.c | 3 ++ 5 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index e93dbc66..0c39e94c 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -31,3 +31,32 @@ char* guac_vnc_get_password(rfbClient* client) { return ((guac_vnc_client*) gc->data)->settings->password; } +rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + rfbCredential *creds = malloc(sizeof(rfbCredential)); + + if (credentialType == rfbCredentialTypeUser) { + creds->userCredential.username = ((guac_vnc_client*) gc->data)->settings->username; + creds->userCredential.password = ((guac_vnc_client*) gc->data)->settings->password; + return creds; + } + + else if (credentialType == rfbCredentialTypeX509) { + creds->x509Credential.x509ClientCertFile = ((guac_vnc_client*) gc->data)->settings->client_cert; + creds->x509Credential.x509ClientKeyFile = ((guac_vnc_client*) gc->data)->settings->client_key; + creds->x509Credential.x509CACertFile = ((guac_vnc_client*) gc->data)->settings->ca_cert; + creds->x509Credential.x509CACRLFile = ((guac_vnc_client*) gc->data)->settings->ca_crl; + + if (creds->x509Credential.x509CACRLFile != NULL) + creds->x509Credential.x509CrlVerifyMode = 2; + else + creds->x509Credential.x509CrlVerifyMode = 0; + + return creds; + } + + guac_client_log(client, GUAC_LOG_ERROR, + "Unknown credential type requested."); + return NULL; + +} diff --git a/src/protocols/vnc/auth.h b/src/protocols/vnc/auth.h index 615d80a1..e978aa95 100644 --- a/src/protocols/vnc/auth.h +++ b/src/protocols/vnc/auth.h @@ -27,7 +27,7 @@ /** * Callback which is invoked by libVNCServer when it needs to read the user's - * VNC password. As ths user's password, if any, will be stored in the + * VNC password. As this user's password, if any, will be stored in the * connection settings, this function does nothing more than return that value. * * @param client @@ -38,5 +38,19 @@ */ char* guac_vnc_get_password(rfbClient* client); +/** + * Callback which is invoked by libVNCServer when it needs to read the user's + * VNC credentials. The credentials are stored in the connection settings, + * so they will be retrieved from that. + * + * @param client + * The rfbClient associated with the VNC connection requiring the + * authentication. + * + * @return + * The rfbCredential object that contains the required credentials. + */ +rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType); + #endif diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index a21aea8c..24f1ff61 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -35,7 +35,12 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "port", "read-only", "encodings", + "username", "password", + "client-cert", + "client-key", + "ca-cert", + "ca-crl", "swap-red-blue", "color-depth", "cursor", @@ -108,10 +113,37 @@ enum VNC_ARGS_IDX { */ IDX_ENCODINGS, + /** + * The username to send to the VNC server if authentication is requested. + */ + IDX_USERNAME, + /** * The password to send to the VNC server if authentication is requested. */ IDX_PASSWORD, + + /** + * The client certificate to send to the VNC server if x509 authentication + * is being used. + */ + IDX_CLIENT_CERT, + + /** + * The client private key to send to the VNC server if x509 authentication + * is being used. + */ + IDX_CLIENT_KEY, + + /** + * The CA certificate to use when performing x509 authentication. + */ + IDX_CA_CERT, + + /** + * The location of the CA CRL to use when performing x509 authentication. + */ + IDX_CA_CRL, /** * "true" if the red and blue components of each color should be swapped, @@ -337,10 +369,30 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, guac_user_parse_args_int(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_PORT, 0); + settings->username = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_USERNAME, ""); /* NOTE: freed by libvncclient */ + settings->password = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_PASSWORD, ""); /* NOTE: freed by libvncclient */ + settings->client_cert = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CLIENT_CERT, NULL); + + settings->client_key = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CLIENT_KEY, NULL); + + settings->ca_cert = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CA_CERT, NULL); + + settings->ca_crl = + guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, + IDX_CA_CRL, NULL); + /* Remote cursor */ if (strcmp(argv[IDX_CURSOR], "remote") == 0) { guac_user_log(user, GUAC_LOG_INFO, "Cursor rendering: remote"); @@ -530,6 +582,10 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { free(settings->hostname); free(settings->recording_name); free(settings->recording_path); + free(settings->client_cert); + free(settings->client_key); + free(settings->ca_cert); + free(settings->ca_crl); #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 13a3d87b..078f7f0c 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -45,10 +45,36 @@ typedef struct guac_vnc_settings { */ int port; + /** + * The username given in the arguments. + */ + char* username; + /** * The password given in the arguments. */ char* password; + + /** + * The client certificate to use for authentication. + */ + char* client_cert; + + /** + * The client private key to use for authentication. + */ + char* client_key; + + /** + * The CA certificate file to use for authentication. + */ + char* ca_cert; + + /** + * The CA CRL location to use for checking for revoked certificates during + * authentication. + */ + char* ca_crl; /** * Space-separated list of encodings to use within the VNC session. diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 17033a67..f33b267a 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -153,6 +153,9 @@ rfbClient* guac_vnc_get_client(guac_client* client) { } + /* Authentication */ + rfb_client->GetCredential = guac_vnc_get_credentials; + /* Password */ rfb_client->GetPassword = guac_vnc_get_password; From 90d55956d0f42a6210dbec3f5561ad9b1ed20218 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 2 Aug 2019 23:20:44 -0400 Subject: [PATCH 167/175] GUACAMOLE-514: use correct client in log call. --- src/protocols/vnc/auth.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 0c39e94c..e03be531 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -55,8 +55,7 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { return creds; } - guac_client_log(client, GUAC_LOG_ERROR, - "Unknown credential type requested."); + guac_client_log(gc, GUAC_LOG_ERROR, "Unknown credential type requested."); return NULL; } From 4329739d3ef7a72a0595d19f5a0979c9d8f5aed0 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 2 Aug 2019 23:29:03 -0400 Subject: [PATCH 168/175] GUACAMOLE-514: Remove CRL check flag. --- src/protocols/vnc/auth.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index e03be531..95ce7b86 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -45,13 +45,7 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { creds->x509Credential.x509ClientCertFile = ((guac_vnc_client*) gc->data)->settings->client_cert; creds->x509Credential.x509ClientKeyFile = ((guac_vnc_client*) gc->data)->settings->client_key; creds->x509Credential.x509CACertFile = ((guac_vnc_client*) gc->data)->settings->ca_cert; - creds->x509Credential.x509CACRLFile = ((guac_vnc_client*) gc->data)->settings->ca_crl; - - if (creds->x509Credential.x509CACRLFile != NULL) - creds->x509Credential.x509CrlVerifyMode = 2; - else - creds->x509Credential.x509CrlVerifyMode = 0; - + creds->x509Credential.x509CACrlFile = ((guac_vnc_client*) gc->data)->settings->ca_crl; return creds; } From b9001f4ec7619726a9d3e75078101cbda10e26c1 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sat, 3 Aug 2019 12:17:49 -0400 Subject: [PATCH 169/175] GUACAMOLE-514: Add missing parameter tag. --- src/protocols/vnc/auth.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/protocols/vnc/auth.h b/src/protocols/vnc/auth.h index e978aa95..155a48db 100644 --- a/src/protocols/vnc/auth.h +++ b/src/protocols/vnc/auth.h @@ -47,6 +47,10 @@ char* guac_vnc_get_password(rfbClient* client); * The rfbClient associated with the VNC connection requiring the * authentication. * + * @param credentialType + * The credential type being requested, as defined by the libVNCclient + * code in the rfbclient.h header. + * * @return * The rfbCredential object that contains the required credentials. */ From 88425160ae4c7a25b4101188a107dae0f1902f57 Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 4 Aug 2019 18:35:23 -0400 Subject: [PATCH 170/175] GUACAMOLE-514: Write x509 authentication factors to temp files. --- src/protocols/vnc/auth.c | 43 +++++++++++++++++++++++++++++++----- src/protocols/vnc/settings.c | 21 ++++++++++++++++++ src/protocols/vnc/settings.h | 30 ++++++++++++++++++++----- 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 95ce7b86..f0b2ed5c 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -34,18 +34,49 @@ char* guac_vnc_get_password(rfbClient* client) { rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); rfbCredential *creds = malloc(sizeof(rfbCredential)); + guac_vnc_settings* settings = ((guac_vnc_client*) gc->data)->settings; if (credentialType == rfbCredentialTypeUser) { - creds->userCredential.username = ((guac_vnc_client*) gc->data)->settings->username; - creds->userCredential.password = ((guac_vnc_client*) gc->data)->settings->password; + creds->userCredential.username = settings->username; + creds->userCredential.password = settings->password; return creds; } else if (credentialType == rfbCredentialTypeX509) { - creds->x509Credential.x509ClientCertFile = ((guac_vnc_client*) gc->data)->settings->client_cert; - creds->x509Credential.x509ClientKeyFile = ((guac_vnc_client*) gc->data)->settings->client_key; - creds->x509Credential.x509CACertFile = ((guac_vnc_client*) gc->data)->settings->ca_cert; - creds->x509Credential.x509CACrlFile = ((guac_vnc_client*) gc->data)->settings->ca_crl; + char* template = "guac_XXXXXX"; + + if (settings->client_cert != NULL) { + settings->client_cert_temp = strdup(template); + int cert_fd = mkstemp(settings->client_cert_temp); + write(cert_fd, settings->client_cert, strlen(settings->client_cert)); + close(cert_fd); + creds->x509Credential.x509ClientCertFile = settings->client_cert_temp; + } + + if (settings->client_key != NULL) { + settings->client_key_temp = strdup(template); + int key_fd = mkstemp(settings->client_key_temp); + write(key_fd, settings->client_key, strlen(settings->client_key)); + close(key_fd); + creds->x509Credential.x509ClientKeyFile = settings->client_key_temp; + } + + if (settings->ca_cert != NULL) { + settings->ca_cert_temp = strdup(template); + int ca_fd = mkstemp(settings->ca_cert_temp); + write(ca_fd, settings->ca_cert, strlen(settings->ca_cert)); + close(ca_fd); + creds->x509Credential.x509CACertFile = settings->ca_cert_temp; + } + + if (settings->ca_crl != NULL) { + settings->ca_crl_temp = strdup(template); + int crl_fd = mkstemp(settings->ca_crl_temp); + write(crl_fd, settings->ca_crl, strlen(settings->ca_crl)); + close(crl_fd); + creds->x509Credential.x509CACrlFile = settings->ca_crl_temp; + } + return creds; } diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index 24f1ff61..a38aac6e 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -28,6 +28,7 @@ #include #include #include +#include /* Client plugin arguments */ const char* GUAC_VNC_CLIENT_ARGS[] = { @@ -586,6 +587,26 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { free(settings->client_key); free(settings->ca_cert); free(settings->ca_crl); + + if (settings->client_cert_temp != NULL) { + unlink(settings->client_cert_temp); + free(settings->client_cert_temp); + } + + if (settings->client_key_temp != NULL) { + unlink(settings->client_key_temp); + free(settings->client_key_temp); + } + + if (settings->ca_cert_temp != NULL) { + unlink(settings->ca_cert_temp); + free(settings->ca_cert_temp); + } + + if (settings->ca_crl_temp != NULL) { + unlink(settings->ca_crl_temp); + free(settings->ca_crl_temp); + } #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 078f7f0c..18570b0b 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -56,25 +56,45 @@ typedef struct guac_vnc_settings { char* password; /** - * The client certificate to use for authentication. + * The contents of the client certificate to use for authentication. */ char* client_cert; /** - * The client private key to use for authentication. + * The location of the temporary client certificate file. + */ + char* client_cert_temp; + + /** + * The contents of the client private key to use for authentication. */ char* client_key; /** - * The CA certificate file to use for authentication. + * The location of the temporary client key file. + */ + char* client_key_temp; + + /** + * The contents of the CA certificate file to use for authentication. */ char* ca_cert; /** - * The CA CRL location to use for checking for revoked certificates during - * authentication. + * The location of the temporary CA file. + */ + char* ca_cert_temp; + + /** + * The contents of the CA CRL location to use for checking for revoked + * certificates during authentication. */ char* ca_crl; + + /** + * The location of the temporary CRL file. + */ + char* ca_crl_temp; /** * Space-separated list of encodings to use within the VNC session. From f21621e677804ee39e3a2418e114f064034c76ba Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Thu, 8 Aug 2019 16:19:01 -0400 Subject: [PATCH 171/175] GUACAMOLE-514: Remove x509 support. --- src/protocols/vnc/auth.c | 43 ++--------------------- src/protocols/vnc/settings.c | 67 ------------------------------------ src/protocols/vnc/settings.h | 41 ---------------------- 3 files changed, 3 insertions(+), 148 deletions(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index f0b2ed5c..72f60bd1 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -33,54 +33,17 @@ char* guac_vnc_get_password(rfbClient* client) { rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); - rfbCredential *creds = malloc(sizeof(rfbCredential)); guac_vnc_settings* settings = ((guac_vnc_client*) gc->data)->settings; if (credentialType == rfbCredentialTypeUser) { + rfbCredential *creds = malloc(sizeof(rfbCredential)); creds->userCredential.username = settings->username; creds->userCredential.password = settings->password; return creds; } - else if (credentialType == rfbCredentialTypeX509) { - char* template = "guac_XXXXXX"; - - if (settings->client_cert != NULL) { - settings->client_cert_temp = strdup(template); - int cert_fd = mkstemp(settings->client_cert_temp); - write(cert_fd, settings->client_cert, strlen(settings->client_cert)); - close(cert_fd); - creds->x509Credential.x509ClientCertFile = settings->client_cert_temp; - } - - if (settings->client_key != NULL) { - settings->client_key_temp = strdup(template); - int key_fd = mkstemp(settings->client_key_temp); - write(key_fd, settings->client_key, strlen(settings->client_key)); - close(key_fd); - creds->x509Credential.x509ClientKeyFile = settings->client_key_temp; - } - - if (settings->ca_cert != NULL) { - settings->ca_cert_temp = strdup(template); - int ca_fd = mkstemp(settings->ca_cert_temp); - write(ca_fd, settings->ca_cert, strlen(settings->ca_cert)); - close(ca_fd); - creds->x509Credential.x509CACertFile = settings->ca_cert_temp; - } - - if (settings->ca_crl != NULL) { - settings->ca_crl_temp = strdup(template); - int crl_fd = mkstemp(settings->ca_crl_temp); - write(crl_fd, settings->ca_crl, strlen(settings->ca_crl)); - close(crl_fd); - creds->x509Credential.x509CACrlFile = settings->ca_crl_temp; - } - - return creds; - } - - guac_client_log(gc, GUAC_LOG_ERROR, "Unknown credential type requested."); + guac_client_log(gc, GUAC_LOG_ERROR, + "Unsupported credential type requested."); return NULL; } diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index a38aac6e..21f64057 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -28,7 +28,6 @@ #include #include #include -#include /* Client plugin arguments */ const char* GUAC_VNC_CLIENT_ARGS[] = { @@ -38,10 +37,6 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "encodings", "username", "password", - "client-cert", - "client-key", - "ca-cert", - "ca-crl", "swap-red-blue", "color-depth", "cursor", @@ -123,28 +118,6 @@ enum VNC_ARGS_IDX { * The password to send to the VNC server if authentication is requested. */ IDX_PASSWORD, - - /** - * The client certificate to send to the VNC server if x509 authentication - * is being used. - */ - IDX_CLIENT_CERT, - - /** - * The client private key to send to the VNC server if x509 authentication - * is being used. - */ - IDX_CLIENT_KEY, - - /** - * The CA certificate to use when performing x509 authentication. - */ - IDX_CA_CERT, - - /** - * The location of the CA CRL to use when performing x509 authentication. - */ - IDX_CA_CRL, /** * "true" if the red and blue components of each color should be swapped, @@ -377,22 +350,6 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, settings->password = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, IDX_PASSWORD, ""); /* NOTE: freed by libvncclient */ - - settings->client_cert = - guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_CLIENT_CERT, NULL); - - settings->client_key = - guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_CLIENT_KEY, NULL); - - settings->ca_cert = - guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_CA_CERT, NULL); - - settings->ca_crl = - guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_CA_CRL, NULL); /* Remote cursor */ if (strcmp(argv[IDX_CURSOR], "remote") == 0) { @@ -583,30 +540,6 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { free(settings->hostname); free(settings->recording_name); free(settings->recording_path); - free(settings->client_cert); - free(settings->client_key); - free(settings->ca_cert); - free(settings->ca_crl); - - if (settings->client_cert_temp != NULL) { - unlink(settings->client_cert_temp); - free(settings->client_cert_temp); - } - - if (settings->client_key_temp != NULL) { - unlink(settings->client_key_temp); - free(settings->client_key_temp); - } - - if (settings->ca_cert_temp != NULL) { - unlink(settings->ca_cert_temp); - free(settings->ca_cert_temp); - } - - if (settings->ca_crl_temp != NULL) { - unlink(settings->ca_crl_temp); - free(settings->ca_crl_temp); - } #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ diff --git a/src/protocols/vnc/settings.h b/src/protocols/vnc/settings.h index 18570b0b..34c08ec9 100644 --- a/src/protocols/vnc/settings.h +++ b/src/protocols/vnc/settings.h @@ -54,47 +54,6 @@ typedef struct guac_vnc_settings { * The password given in the arguments. */ char* password; - - /** - * The contents of the client certificate to use for authentication. - */ - char* client_cert; - - /** - * The location of the temporary client certificate file. - */ - char* client_cert_temp; - - /** - * The contents of the client private key to use for authentication. - */ - char* client_key; - - /** - * The location of the temporary client key file. - */ - char* client_key_temp; - - /** - * The contents of the CA certificate file to use for authentication. - */ - char* ca_cert; - - /** - * The location of the temporary CA file. - */ - char* ca_cert_temp; - - /** - * The contents of the CA CRL location to use for checking for revoked - * certificates during authentication. - */ - char* ca_crl; - - /** - * The location of the temporary CRL file. - */ - char* ca_crl_temp; /** * Space-separated list of encodings to use within the VNC session. From 61d12f16680b09f4f48c8bb099434ff61f56b26a Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Fri, 9 Aug 2019 13:08:36 -0400 Subject: [PATCH 172/175] GUACAMOLE-514: VNC client should abort on credential error. --- src/protocols/vnc/auth.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 72f60bd1..6374da53 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -41,9 +41,12 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { creds->userCredential.password = settings->password; return creds; } - - guac_client_log(gc, GUAC_LOG_ERROR, + + guac_client_abort(gc, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unsupported credential type requested."); + guac_client_log(gc, GUAC_LOG_DEBUG, + "Unable to provided requested credential %d.", + credentialType); return NULL; } From 7ac840090e2d76ba97698dc4a527812b25c6744f Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Mon, 12 Aug 2019 19:45:45 -0400 Subject: [PATCH 173/175] GUACAMOLE-514: Slight correction to debug message. --- src/protocols/vnc/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 6374da53..8cc6b1d7 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -45,7 +45,7 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { guac_client_abort(gc, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unsupported credential type requested."); guac_client_log(gc, GUAC_LOG_DEBUG, - "Unable to provided requested credential %d.", + "Unable to provide requested type of credential: %d.", credentialType); return NULL; From a189c9ab8ab855f1f4fdee7cbc1c6f1c4a85ccd8 Mon Sep 17 00:00:00 2001 From: Weston Thayer Date: Thu, 8 Aug 2019 20:52:28 -0400 Subject: [PATCH 174/175] GUACAMOLE-859: Remove +ext from Caps Lock keymapping. When connected with a Guacamole RDP session, the keysym for Caps Lock (0xffe5) is sent over RDP as scancode 0xe03a. Windows does not understand this scancode, thus does not generate the correct VK (Virtual Key) events. Removing +ext from the keymap sends the scancode 0x003a, which is correctly recognized by Windows as Caps Lock. This enables Windows applications to listen for key down and key up events on the Caps Lock key while connected via a Guacamole RDP session. Note: this issue likely went un-noticed for a long time because it only impacts the key up and down events for the Caps Lock key, not Caps Lock's effect on sending uppercase [A-Z] characters. --- src/protocols/rdp/keymaps/base.keymap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/keymaps/base.keymap b/src/protocols/rdp/keymaps/base.keymap index c55a37fc..d02c5eab 100644 --- a/src/protocols/rdp/keymaps/base.keymap +++ b/src/protocols/rdp/keymaps/base.keymap @@ -42,7 +42,7 @@ map +ext 0x37 ~ 0xff61 # Print Screen # Locks map 0x45 ~ 0xff7f # Num_Lock map 0x46 ~ 0xff14 # Scroll_Lock -map +ext 0x3A ~ 0xffe5 # Caps_Lock +map 0x3A ~ 0xffe5 # Caps_Lock # Keypad numerals map -shift +num 0x52 ~ 0xffb0 # KP_0 From 4cc9c2d3e1c4c51e6cb4e50620be12e9bc242773 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 4 Aug 2019 23:40:53 +0300 Subject: [PATCH 175/175] GUACAMOLE-861: Fixes WINDOWS_TIME calculation This fixes the UNIX time to FILETIME conversion in WINDOWS_TIME macro, according to MSDN (addition instead of subtraction). --- src/protocols/rdp/rdp_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocols/rdp/rdp_fs.h b/src/protocols/rdp/rdp_fs.h index e8299efc..f1e42956 100644 --- a/src/protocols/rdp/rdp_fs.h +++ b/src/protocols/rdp/rdp_fs.h @@ -188,7 +188,7 @@ * Converts a UNIX timestamp (seconds since Jan 1, 1970 UTC) to Windows * timestamp (100 nanosecond intervals since Jan 1, 1601 UTC). */ -#define WINDOWS_TIME(t) ((t - ((uint64_t) 11644473600)) * 10000000) +#define WINDOWS_TIME(t) ((t + ((uint64_t) 11644473600)) * 10000000) /** * An arbitrary file on the virtual filesystem of the Guacamole drive.