diff --git a/src/guacd/proc.c b/src/guacd/proc.c index aacf2b03..9612dd33 100644 --- a/src/guacd/proc.c +++ b/src/guacd/proc.c @@ -340,6 +340,9 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { owner = 0; } + + /* Enable keep alive on the broadcast socket */ + guac_socket_require_keep_alive(client->socket); cleanup_client: diff --git a/src/libguac/client.c b/src/libguac/client.c index 64404534..9738c0c2 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -478,6 +478,44 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) { } +/** + * A callback function which is invoked by guac_client_owner_send_required() to + * send the required parameters to the specified user, who is the owner of the + * client session. + * + * @param user + * The guac_user that will receive the required parameters, who is the owner + * of the client. + * + * @param data + * A pointer to a NULL-terminated array of required parameters that will be + * passed on to the owner to continue the connection. + * + * @return + * Zero if the operation succeeds or non-zero on failure, cast as a void*. + */ +static void* guac_client_owner_send_required_callback(guac_user* user, void* data) { + + const char** required = (const char **) data; + + /* Send required parameters to owner. */ + if (user != NULL) + return (void*) ((intptr_t) guac_protocol_send_required(user->socket, required)); + + return (void*) ((intptr_t) -1); + +} + +int guac_client_owner_send_required(guac_client* client, const char** required) { + + /* Don't send required instruction if client does not support it. */ + if (!guac_client_owner_supports_required(client)) + return -1; + + return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_send_required_callback, required)); + +} + /** * Updates the provided approximate processing lag, taking into account the * processing lag of the given user. @@ -633,6 +671,36 @@ static void* __webp_support_callback(guac_user* user, void* data) { } #endif +/** + * A callback function which is invoked by guac_client_owner_supports_required() + * to determine if the owner of a client supports the "required" instruction, + * returning zero if the user does not support the instruction or non-zero if + * the user supports it. + * + * @param user + * The guac_user that will be checked for "required" instruction support. + * + * @param data + * Data provided to the callback. This value is never used within this + * callback. + * + * @return + * A non-zero integer if the provided user who owns the connection supports + * the "required" instruction, or zero if the user does not. The integer + * is cast as a void*. + */ +static void* guac_owner_supports_required_callback(guac_user* user, void* data) { + + return (void*) ((intptr_t) guac_user_supports_required(user)); + +} + +int guac_client_owner_supports_required(guac_client* client) { + + return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_required_callback, NULL)); + +} + int guac_client_supports_webp(guac_client* client) { #ifdef ENABLE_WEBP diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h index 71d48f22..4ffe5cf4 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -549,6 +549,22 @@ int guac_client_load_plugin(guac_client* client, const char* protocol); */ int guac_client_get_processing_lag(guac_client* client); +/** + * Sends a request to the owner of the given guac_client for parameters required + * to continue the connection started by the client. The function returns zero + * on success or non-zero on failure. + * + * @param client + * The client where additional connection parameters are required. + * + * @param required + * The NULL-terminated array of required parameters. + * + * @return + * Zero on success, non-zero on failure. + */ +int guac_client_owner_send_required(guac_client* client, const char** required); + /** * Streams the given connection parameter value over an argument value stream * ("argv" instruction), exposing the current value of the named connection @@ -692,6 +708,21 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface, int quality, int lossless); +/** + * Returns whether the owner of the given client supports the "required" + * instruction, returning non-zero if the client owner does support the + * instruction, or zero if the owner does not. + * + * @param client + * The Guacamole client whose owner should be checked for supporting + * the "required" instruction. + * + * @return + * Non-zero if the owner of the given client supports the "required" + * instruction, zero otherwise. + */ +int guac_client_owner_supports_required(guac_client* client); + /** * Returns whether all users of the given client support WebP. If any user does * not support WebP, or the server cannot encode WebP images, zero is returned. diff --git a/src/libguac/guacamole/protocol-constants.h b/src/libguac/guacamole/protocol-constants.h index afa67bb9..5bd91b07 100644 --- a/src/libguac/guacamole/protocol-constants.h +++ b/src/libguac/guacamole/protocol-constants.h @@ -38,7 +38,7 @@ * 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" +#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_3_0" /** * The maximum number of bytes that should be sent in any one blob instruction diff --git a/src/libguac/guacamole/protocol-types.h b/src/libguac/guacamole/protocol-types.h index b85d36e7..51652d2b 100644 --- a/src/libguac/guacamole/protocol-types.h +++ b/src/libguac/guacamole/protocol-types.h @@ -276,5 +276,39 @@ typedef enum guac_line_join_style { GUAC_LINE_JOIN_ROUND = 0x2 } guac_line_join_style; +/** + * The set of protocol versions known to guacd to handle negotiation or feature + * support between differing versions of Guacamole clients and guacd. + */ +typedef enum guac_protocol_version { + + /** + * An unknown version of the Guacamole protocol. + */ + GUAC_PROTOCOL_VERSION_UNKNOWN = 0x000000, + + /** + * Original protocol version 1.0.0, which lacks support for negotiating + * parameters and protocol version, and requires that parameters in the + * client/server handshake be delivered in order. + */ + GUAC_PROTOCOL_VERSION_1_0_0 = 0x010000, + + /** + * Protocol version 1.1.0, which includes support for parameter and version + * negotiation and for sending timezone information from the client + * to the server. + */ + GUAC_PROTOCOL_VERSION_1_1_0 = 0x010100, + + /** + * Protocol version 1.3.0, which supports the "required" instruction, + * allowing connections in guacd to request information from the client and + * await a response. + */ + GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300 + +} guac_protocol_version; + #endif diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index f4e02880..c6dbed29 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -794,6 +794,22 @@ int guac_protocol_send_push(guac_socket* socket, const guac_layer* layer); int guac_protocol_send_rect(guac_socket* socket, const guac_layer* layer, int x, int y, int width, int height); +/** + * Sends a "required" instruction over the given guac_socket connection. This + * instruction indicates to the client that one or more additional parameters + * are needed to continue the connection. + * + * @param socket + * The guac_socket connection to which to send the instruction. + * + * @param required + * A NULL-terminated array of required parameters. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_required(guac_socket* socket, const char** required); + /** * Sends a reset instruction over the given guac_socket connection. * @@ -1007,5 +1023,32 @@ int guac_protocol_send_name(guac_socket* socket, const char* name); */ int guac_protocol_decode_base64(char* base64); +/** + * Given a string representation of a protocol version, return the enum value of + * that protocol version, or GUAC_PROTOCOL_VERSION_UNKNOWN if the value is not a + * known version. + * + * @param version_string + * The string representation of the protocol version. + * + * @return + * The enum value of the protocol version, or GUAC_PROTOCOL_VERSION_UNKNOWN + * if the provided version is not known. + */ +guac_protocol_version guac_protocol_string_to_version(const char* version_string); + +/** + * Given the enum value of the protocol version, return a pointer to the string + * representation of the version, or NULL if the version is unknown. + * + * @param version + * The enum value of the protocol version. + * + * @return + * A pointer to the string representation of the protocol version, or NULL + * if the version is unknown. + */ +const char* guac_protocol_version_to_string(guac_protocol_version version); + #endif diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index 5e56e330..a648c0c2 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -109,6 +109,19 @@ 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); +/** + * Simple wrapper for strdup() which behaves identically to standard strdup(), + * except that NULL will be returned if the provided string is NULL. + * + * @param str + * The string to duplicate as a newly-allocated string. + * + * @return + * A newly-allocated string containing identically the same content as the + * given string, or NULL if the given string was NULL. + */ +char* guac_strdup(const char* str); + /** * Concatenates each of the given strings, separated by the given delimiter, * storing the result within a destination buffer. The number of bytes written diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index fac42b98..de04d2a9 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -95,6 +95,12 @@ struct guac_user_info { * is the standard tzdata naming convention. */ const char* timezone; + + /** + * The Guacamole protocol version that the remote system supports, allowing + * for feature support to be negotiated between client and server. + */ + guac_protocol_version protocol_version; }; @@ -823,6 +829,17 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket, guac_composite_mode mode, const guac_layer* layer, int x, int y, cairo_surface_t* surface, int quality, int lossless); +/** + * Returns whether the given user supports the "required" instruction. + * + * @param user + * The Guacamole user to check for support of the "required" instruction. + * + * @return + * Non-zero if the user supports the "required" instruction, otherwise zero. + */ +int guac_user_supports_required(guac_user* user); + /** * Returns whether the given user supports WebP. If the user does not * support WebP, or the server cannot encode WebP images, zero is returned. diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index ff73a558..59a46d5b 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -23,6 +23,7 @@ #include "guacamole/layer.h" #include "guacamole/object.h" #include "guacamole/protocol.h" +#include "guacamole/protocol-types.h" #include "guacamole/socket.h" #include "guacamole/stream.h" #include "guacamole/unicode.h" @@ -39,6 +40,34 @@ #include #include +/** + * A structure mapping the enum value of a Guacamole protocol version to the + * string representation of the version. + */ +typedef struct guac_protocol_version_mapping { + + /** + * The enum value of the protocol version. + */ + guac_protocol_version version; + + /** + * The string value representing the protocol version. + */ + char* version_string; + +} guac_protocol_version_mapping; + +/** + * The map of known protocol versions to the corresponding string value. + */ +guac_protocol_version_mapping guac_protocol_version_table[] = { + { GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" }, + { GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" }, + { GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" }, + { GUAC_PROTOCOL_VERSION_UNKNOWN, NULL } +}; + /* Output formatting functions */ ssize_t __guac_socket_write_length_string(guac_socket* socket, const char* str) { @@ -66,6 +95,38 @@ ssize_t __guac_socket_write_length_double(guac_socket* socket, double d) { } +/** + * Loop through the provided NULL-terminated array, writing the values in the + * array to the given socket. Values are written as a series of Guacamole + * protocol elements, including the leading comma and the value length in + * addition to the value itself. Returns zero on success, non-zero on error. + * + * @param socket + * The socket to which the data should be written. + * + * @param array + * The NULL-terminated array of values to write. + * + * @return + * Zero on success, non-zero on error. + */ +static int guac_socket_write_array(guac_socket* socket, const char** array) { + + /* Loop through array, writing provided values to the socket. */ + for (int i=0; array[i] != NULL; i++) { + + if (guac_socket_write_string(socket, ",")) + return -1; + + if (__guac_socket_write_length_string(socket, array[i])) + return -1; + + } + + return 0; + +} + /* Protocol functions */ int guac_protocol_send_ack(guac_socket* socket, guac_stream* stream, @@ -90,8 +151,6 @@ int guac_protocol_send_ack(guac_socket* socket, guac_stream* stream, 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 ahead of other args. */ @@ -99,15 +158,8 @@ static int __guac_protocol_send_args(guac_socket* socket, const char** args) { || __guac_socket_write_length_string(socket, GUACAMOLE_PROTOCOL_VERSION)) return -1; - for (i=0; args[i] != NULL; i++) { - - if (guac_socket_write_string(socket, ",")) - return -1; - - if (__guac_socket_write_length_string(socket, args[i])) - return -1; - - } + if (guac_socket_write_array(socket, args)) + return -1; return guac_socket_write_string(socket, ";"); @@ -308,19 +360,11 @@ int guac_protocol_send_close(guac_socket* socket, const guac_layer* layer) { static int __guac_protocol_send_connect(guac_socket* socket, const char** args) { - int i; + if (guac_socket_write_string(socket, "7.connect")) + return -1; - if (guac_socket_write_string(socket, "7.connect")) return -1; - - for (i=0; args[i] != NULL; i++) { - - if (guac_socket_write_string(socket, ",")) - return -1; - - if (__guac_socket_write_length_string(socket, args[i])) - return -1; - - } + if (guac_socket_write_array(socket, args)) + return -1; return guac_socket_write_string(socket, ";"); @@ -961,6 +1005,23 @@ int guac_protocol_send_rect(guac_socket* socket, } +int guac_protocol_send_required(guac_socket* socket, const char** required) { + + int ret_val; + + guac_socket_instruction_begin(socket); + + ret_val = guac_socket_write_string(socket, "8.required") + || guac_socket_write_array(socket, required) + || guac_socket_write_string(socket, ";") + || guac_socket_flush(socket); + + guac_socket_instruction_end(socket); + + return ret_val; + +} + int guac_protocol_send_reset(guac_socket* socket, const guac_layer* layer) { int ret_val; @@ -1241,3 +1302,35 @@ int guac_protocol_decode_base64(char* base64) { } +guac_protocol_version guac_protocol_string_to_version(const char* version_string) { + + guac_protocol_version_mapping* current = guac_protocol_version_table; + while (current->version != GUAC_PROTOCOL_VERSION_UNKNOWN) { + + if (strcmp(current->version_string, version_string) == 0) + return current->version; + + current++; + + } + + return GUAC_PROTOCOL_VERSION_UNKNOWN; + +} + +const char* guac_protocol_version_to_string(guac_protocol_version version) { + + guac_protocol_version_mapping* current = guac_protocol_version_table; + while (current->version != GUAC_PROTOCOL_VERSION_UNKNOWN) { + + if (current->version == version) + return (const char*) current->version_string; + + current++; + + } + + return NULL; + +} + diff --git a/src/libguac/string.c b/src/libguac/string.c index f05c4c06..e75f7c18 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -81,6 +81,17 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { } +char* guac_strdup(const char* str) { + + /* Return NULL if no string provided */ + if (str == NULL) + return NULL; + + /* Otherwise just invoke strdup() */ + return strdup(str); + +} + size_t guac_strljoin(char* restrict dest, const char* restrict const* elements, int nmemb, const char* restrict delim, size_t n) { diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index 4ab3269f..7ee55948 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -40,8 +40,10 @@ test_libguac_SOURCES = \ parser/read.c \ pool/next_free.c \ protocol/base64_decode.c \ + protocol/guac_protocol_version.c \ socket/fd_send_instruction.c \ socket/nested_send_instruction.c \ + string/strdup.c \ string/strlcat.c \ string/strlcpy.c \ string/strljoin.c \ diff --git a/src/libguac/tests/protocol/guac_protocol_version.c b/src/libguac/tests/protocol/guac_protocol_version.c new file mode 100644 index 00000000..37f42e0f --- /dev/null +++ b/src/libguac/tests/protocol/guac_protocol_version.c @@ -0,0 +1,69 @@ +/* + * 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 + +/** + * Test which verifies that conversion of the guac_protocol_version enum to + * string values succeeds and produces the expected results. + */ +void test_guac_protocol__version_to_string() { + + guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_3_0; + guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0; + guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN; + + CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_3_0"); + CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0"); + CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c)); + +} + +/** + * Test which verifies that the version of String representations of Guacamole + * protocol versions are successfully converted into their matching + * guac_protocol_version enum values, and that versions that do not match + * any version get the correct unknown value. + */ +void test_guac_protocol__string_to_version() { + + char* str_version_a = "VERSION_1_3_0"; + char* str_version_b = "VERSION_1_1_0"; + char* str_version_c = "AVACADO"; + char* str_version_d = "VERSION_31_4_1"; + + CU_ASSERT_EQUAL(guac_protocol_string_to_version(str_version_a), GUAC_PROTOCOL_VERSION_1_3_0); + CU_ASSERT_EQUAL(guac_protocol_string_to_version(str_version_b), GUAC_PROTOCOL_VERSION_1_1_0); + CU_ASSERT_EQUAL(guac_protocol_string_to_version(str_version_c), GUAC_PROTOCOL_VERSION_UNKNOWN); + CU_ASSERT_EQUAL(guac_protocol_string_to_version(str_version_d), GUAC_PROTOCOL_VERSION_UNKNOWN); + +} + +/** + * Test which verifies that the comparisons between guac_protocol_version enum + * values produces the expected results. + */ +void test_gauc_protocol__version_comparison() { + + CU_ASSERT_TRUE(GUAC_PROTOCOL_VERSION_1_3_0 > GUAC_PROTOCOL_VERSION_1_0_0); + CU_ASSERT_TRUE(GUAC_PROTOCOL_VERSION_UNKNOWN < GUAC_PROTOCOL_VERSION_1_1_0); + +} \ No newline at end of file diff --git a/src/libguac/tests/string/strdup.c b/src/libguac/tests/string/strdup.c new file mode 100644 index 00000000..0b1e76b3 --- /dev/null +++ b/src/libguac/tests/string/strdup.c @@ -0,0 +1,49 @@ +/* + * 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 + +/** + * Source test string for copying. + */ +const char* source_string = "Mashing avocados."; + +/** + * A NULL string variable for copying to insure that NULL is copied properly. + */ +const char* null_string = NULL; + +/** + * Verify guac_strdup() behavior when the string is both NULL and not NULL. + */ +void test_string__strdup() { + + /* Copy the strings. */ + char* dest_string = guac_strdup(source_string); + char* null_copy = guac_strdup(null_string); + + /* Run the tests. */ + CU_ASSERT_STRING_EQUAL(dest_string, "Mashing avocados."); + CU_ASSERT_PTR_NULL(null_copy); + +} \ No newline at end of file diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c index 89afccdd..bc9992b2 100644 --- a/src/libguac/user-handshake.c +++ b/src/libguac/user-handshake.c @@ -344,12 +344,16 @@ 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) + if (strcmp(parser->argv[0],"") != 0) { guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol " "version \"%s\"", parser->argv[0]); - else + user->info.protocol_version = guac_protocol_string_to_version(parser->argv[0]); + } + else { guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined " "its protocol version."); + user->info.protocol_version = GUAC_PROTOCOL_VERSION_1_0_0; + } /* Handle user I/O, wait for connection to terminate */ guac_user_start(parser, user, usec_timeout); diff --git a/src/libguac/user.c b/src/libguac/user.c index aaa51bb9..4320ca77 100644 --- a/src/libguac/user.c +++ b/src/libguac/user.c @@ -316,6 +316,15 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket, } +int guac_user_supports_required(guac_user* user) { + + if (user == NULL) + return 0; + + return (user->info.protocol_version >= GUAC_PROTOCOL_VERSION_1_3_0); + +} + int guac_user_supports_webp(guac_user* user) { #ifdef ENABLE_WEBP diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index ad949b89..e8ddfeb4 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -38,6 +38,7 @@ nodist_libguac_client_rdp_la_SOURCES = \ _generated_keymaps.c libguac_client_rdp_la_SOURCES = \ + argv.c \ beep.c \ bitmap.c \ channels/audio-input/audio-buffer.c \ @@ -82,6 +83,7 @@ libguac_client_rdp_la_SOURCES = \ user.c noinst_HEADERS = \ + argv.h \ beep.h \ bitmap.h \ channels/audio-input/audio-buffer.h \ diff --git a/src/protocols/rdp/argv.c b/src/protocols/rdp/argv.c new file mode 100644 index 00000000..c6c922b9 --- /dev/null +++ b/src/protocols/rdp/argv.c @@ -0,0 +1,60 @@ +/* + * 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 "rdp.h" +#include "settings.h" + +#include +#include +#include + +#include +#include +#include + +int guac_rdp_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_settings* settings = rdp_client->settings; + + /* Update username */ + if (strcmp(name, GUAC_RDP_ARGV_USERNAME) == 0) { + free(settings->username); + settings->username = strdup(value); + } + + /* Update password */ + else if (strcmp(name, GUAC_RDP_ARGV_PASSWORD) == 0) { + free(settings->password); + settings->password = strdup(value); + } + + /* Update domain */ + else if (strcmp(name, GUAC_RDP_ARGV_DOMAIN) == 0) { + free(settings->domain); + settings->domain = strdup(value); + } + + return 0; + +} \ No newline at end of file diff --git a/src/protocols/rdp/argv.h b/src/protocols/rdp/argv.h new file mode 100644 index 00000000..c15aba92 --- /dev/null +++ b/src/protocols/rdp/argv.h @@ -0,0 +1,54 @@ +/* + * 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_RDP_ARGV_H +#define GUAC_RDP_ARGV_H + +#include "config.h" + +#include +#include + +/** + * Handles a received argument value from a Guacamole "argv" instruction, + * updating the given connection parameter. + */ +guac_argv_callback guac_rdp_argv_callback; + +/** + * The name of the parameter that specifies/updates the username that will be + * sent to the RDP server during authentication. + */ +#define GUAC_RDP_ARGV_USERNAME "username" + +/** + * The name of the parameter that specifies/updates the password that will be + * sent to the RDP server during authentication. + */ +#define GUAC_RDP_ARGV_PASSWORD "password" + +/** + * The name of the parameter that specifies/updates the domain name that will be + * sent to the RDP server during authentication. + */ +#define GUAC_RDP_ARGV_DOMAIN "domain" + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index 3d7702c6..b31be494 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -17,6 +17,7 @@ * under the License. */ +#include "argv.h" #include "beep.h" #include "bitmap.h" #include "channels/audio-input/audio-buffer.h" @@ -42,6 +43,7 @@ #include "pointer.h" #include "print-job.h" #include "rdp.h" +#include "settings.h" #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" @@ -64,10 +66,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -200,10 +204,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { } /** - * Callback invoked by FreeRDP when authentication is required but a username - * and password has not already been given. In the case of Guacamole, this - * function always succeeds but does not populate the usename or password. The - * username/password must be given within the connection parameters. + * Callback invoked by FreeRDP when authentication is required but the required + * parameters have not been provided. In the case of Guacamole clients that + * support the "required" instruction, this function will send any of the three + * unpopulated RDP authentication parameters back to the client so that the + * connection owner can provide the required information. If the values have + * been provided in the original connection parameters the user will not be + * prompted for updated parameters. If the version of Guacamole Client in use + * by the connection owner does not support the "required" instruction then the + * connection will fail. This function always returns true. * * @param instance * The FreeRDP instance associated with the RDP session requesting @@ -227,10 +236,63 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, rdpContext* context = instance->context; guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_settings* settings = rdp_client->settings; + char* params[4] = {NULL}; + int i = 0; + + /* If the client does not support the "required" instruction, warn and + * quit. + */ + if (!guac_client_owner_supports_required(client)) { + guac_client_log(client, GUAC_LOG_WARNING, "Client does not support the " + "\"required\" instruction. No authentication parameters will " + "be requested."); + return TRUE; + } - /* Warn if connection is likely to fail due to lack of credentials */ - guac_client_log(client, GUAC_LOG_INFO, - "Authentication requested but username or password not given"); + /* If the username is undefined, add it to the requested parameters. */ + if (settings->username == NULL) { + guac_argv_register(GUAC_RDP_ARGV_USERNAME, guac_rdp_argv_callback, NULL, 0); + params[i] = GUAC_RDP_ARGV_USERNAME; + i++; + + /* If username is undefined and domain is also undefined, request domain. */ + if (settings->domain == NULL) { + guac_argv_register(GUAC_RDP_ARGV_DOMAIN, guac_rdp_argv_callback, NULL, 0); + params[i] = GUAC_RDP_ARGV_DOMAIN; + i++; + } + + } + + /* If the password is undefined, add it to the requested parameters. */ + if (settings->password == NULL) { + guac_argv_register(GUAC_RDP_ARGV_PASSWORD, guac_rdp_argv_callback, NULL, 0); + params[i] = GUAC_RDP_ARGV_PASSWORD; + i++; + } + + /* NULL-terminate the array. */ + params[i] = NULL; + + if (i > 0) { + + /* Send required parameters to the owner and wait for the response. */ + guac_client_owner_send_required(client, (const char**) params); + guac_argv_await((const char**) params); + + /* Free old values and get new values from settings. */ + free(*username); + free(*password); + free(*domain); + *username = guac_strdup(settings->username); + *password = guac_strdup(settings->password); + *domain = guac_strdup(settings->domain); + + } + + /* Always return TRUE allowing connection to retry. */ return TRUE; } diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 307c83ba..57760d93 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -17,6 +17,7 @@ * under the License. */ +#include "argv.h" #include "common/defaults.h" #include "common/string.h" #include "config.h" @@ -42,9 +43,9 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "hostname", "port", - "domain", - "username", - "password", + GUAC_RDP_ARGV_DOMAIN, + GUAC_RDP_ARGV_USERNAME, + GUAC_RDP_ARGV_PASSWORD, "width", "height", "dpi", @@ -1266,47 +1267,25 @@ static int guac_rdp_get_performance_flags(guac_rdp_settings* guac_settings) { } -/** - * Simple wrapper for strdup() which behaves identically to standard strdup(), - * execpt that NULL will be returned if the provided string is NULL. - * - * @param str - * The string to duplicate as a newly-allocated string. - * - * @return - * A newly-allocated string containing identically the same content as the - * given string, or NULL if the given string was NULL. - */ -static char* guac_rdp_strdup(const char* str) { - - /* Return NULL if no string provided */ - if (str == NULL) - return NULL; - - /* Otherwise just invoke strdup() */ - return strdup(str); - -} - void guac_rdp_push_settings(guac_client* client, guac_rdp_settings* guac_settings, freerdp* rdp) { rdpSettings* rdp_settings = rdp->settings; /* Authentication */ - rdp_settings->Domain = guac_rdp_strdup(guac_settings->domain); - rdp_settings->Username = guac_rdp_strdup(guac_settings->username); - rdp_settings->Password = guac_rdp_strdup(guac_settings->password); + rdp_settings->Domain = guac_strdup(guac_settings->domain); + rdp_settings->Username = guac_strdup(guac_settings->username); + rdp_settings->Password = guac_strdup(guac_settings->password); /* Connection */ - rdp_settings->ServerHostname = guac_rdp_strdup(guac_settings->hostname); + rdp_settings->ServerHostname = guac_strdup(guac_settings->hostname); rdp_settings->ServerPort = guac_settings->port; /* Session */ rdp_settings->ColorDepth = guac_settings->color_depth; rdp_settings->DesktopWidth = guac_settings->width; rdp_settings->DesktopHeight = guac_settings->height; - rdp_settings->AlternateShell = guac_rdp_strdup(guac_settings->initial_program); + rdp_settings->AlternateShell = guac_strdup(guac_settings->initial_program); rdp_settings->KeyboardLayout = guac_settings->server_layout->freerdp_keyboard_layout; /* Performance flags */ @@ -1424,9 +1403,9 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->Workarea = TRUE; rdp_settings->RemoteApplicationMode = TRUE; rdp_settings->RemoteAppLanguageBarSupported = TRUE; - rdp_settings->RemoteApplicationProgram = guac_rdp_strdup(guac_settings->remote_app); - rdp_settings->ShellWorkingDirectory = guac_rdp_strdup(guac_settings->remote_app_dir); - rdp_settings->RemoteApplicationCmdLine = guac_rdp_strdup(guac_settings->remote_app_args); + rdp_settings->RemoteApplicationProgram = guac_strdup(guac_settings->remote_app); + rdp_settings->ShellWorkingDirectory = guac_strdup(guac_settings->remote_app_dir); + rdp_settings->RemoteApplicationCmdLine = guac_strdup(guac_settings->remote_app_args); } /* Preconnection ID */ @@ -1440,7 +1419,7 @@ void guac_rdp_push_settings(guac_client* client, if (guac_settings->preconnection_blob != NULL) { rdp_settings->NegotiateSecurityLayer = FALSE; rdp_settings->SendPreconnectionPdu = TRUE; - rdp_settings->PreconnectionBlob = guac_rdp_strdup(guac_settings->preconnection_blob); + rdp_settings->PreconnectionBlob = guac_strdup(guac_settings->preconnection_blob); } /* Enable use of RD gateway if a gateway hostname is provided */ @@ -1450,20 +1429,20 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->GatewayEnabled = TRUE; /* RD gateway connection details */ - rdp_settings->GatewayHostname = guac_rdp_strdup(guac_settings->gateway_hostname); + rdp_settings->GatewayHostname = guac_strdup(guac_settings->gateway_hostname); rdp_settings->GatewayPort = guac_settings->gateway_port; /* RD gateway credentials */ rdp_settings->GatewayUseSameCredentials = FALSE; - rdp_settings->GatewayDomain = guac_rdp_strdup(guac_settings->gateway_domain); - rdp_settings->GatewayUsername = guac_rdp_strdup(guac_settings->gateway_username); - rdp_settings->GatewayPassword = guac_rdp_strdup(guac_settings->gateway_password); + rdp_settings->GatewayDomain = guac_strdup(guac_settings->gateway_domain); + rdp_settings->GatewayUsername = guac_strdup(guac_settings->gateway_username); + rdp_settings->GatewayPassword = guac_strdup(guac_settings->gateway_password); } /* Store load balance info (and calculate length) if provided */ if (guac_settings->load_balance_info != NULL) { - rdp_settings->LoadBalanceInfo = (BYTE*) guac_rdp_strdup(guac_settings->load_balance_info); + rdp_settings->LoadBalanceInfo = (BYTE*) guac_strdup(guac_settings->load_balance_info); rdp_settings->LoadBalanceInfoLength = strlen(guac_settings->load_balance_info); } diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index ef401965..351c67e6 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -33,6 +33,7 @@ #include "sftp.h" #endif +#include #include #include #include @@ -116,6 +117,10 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { /* Inbound arbitrary named pipes */ user->pipe_handler = guac_rdp_pipe_svc_pipe_handler; + + /* If we own it, register handler for updating parameters during connection. */ + if (user->owner) + user->argv_handler = guac_argv_handler; } diff --git a/src/protocols/vnc/Makefile.am b/src/protocols/vnc/Makefile.am index fcba33f1..64e8147e 100644 --- a/src/protocols/vnc/Makefile.am +++ b/src/protocols/vnc/Makefile.am @@ -29,6 +29,7 @@ ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = libguac-client-vnc.la libguac_client_vnc_la_SOURCES = \ + argv.c \ auth.c \ client.c \ clipboard.c \ @@ -41,6 +42,7 @@ libguac_client_vnc_la_SOURCES = \ vnc.c noinst_HEADERS = \ + argv.h \ auth.h \ client.h \ clipboard.h \ diff --git a/src/protocols/vnc/argv.c b/src/protocols/vnc/argv.c new file mode 100644 index 00000000..e59b1de7 --- /dev/null +++ b/src/protocols/vnc/argv.c @@ -0,0 +1,53 @@ +/* + * 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 "vnc.h" + +#include +#include +#include + +#include +#include +#include + +int guac_vnc_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data) { + + guac_client* client = user->client; + guac_vnc_client* vnc_client = (guac_vnc_client*) client->data; + guac_vnc_settings* settings = vnc_client->settings; + + /* Update username */ + if (strcmp(name, GUAC_VNC_ARGV_USERNAME) == 0) { + free(settings->username); + settings->username = strdup(value); + } + + /* Update password */ + else if (strcmp(name, GUAC_VNC_ARGV_PASSWORD) == 0) { + free(settings->password); + settings->password = strdup(value); + } + + return 0; + +} \ No newline at end of file diff --git a/src/protocols/vnc/argv.h b/src/protocols/vnc/argv.h new file mode 100644 index 00000000..67777820 --- /dev/null +++ b/src/protocols/vnc/argv.h @@ -0,0 +1,47 @@ +/* + * 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 ARGV_H +#define ARGV_H + +#include "config.h" + +#include +#include + +/** + * Handles a received argument value from a Guacamole "argv" instruction, + * updating the given connection parameter. + */ +guac_argv_callback guac_vnc_argv_callback; + +/** + * The name of the parameter Guacamole will use to specify/update the username + * for the VNC connection. + */ +#define GUAC_VNC_ARGV_USERNAME "username" + +/** + * The name of the parameter Guacamole will use to specify/update the password + * for the VNC connection. + */ +#define GUAC_VNC_ARGV_PASSWORD "password" + +#endif /* ARGV_H */ + diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index 8cc6b1d7..c26e4d2e 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -19,27 +19,94 @@ #include "config.h" +#include "argv.h" #include "auth.h" #include "vnc.h" +#include #include +#include +#include +#include #include #include +#include +#include + char* guac_vnc_get_password(rfbClient* client) { guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); - return ((guac_vnc_client*) gc->data)->settings->password; + guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data); + guac_vnc_settings* settings = vnc_client->settings; + + /* If the client does not support the "required" instruction, just return + the configuration data. */ + if (!guac_client_owner_supports_required(gc)) + return guac_strdup(settings->password); + + /* If password isn't around, prompt for it. */ + if (settings->password == NULL) { + + guac_argv_register(GUAC_VNC_ARGV_PASSWORD, guac_vnc_argv_callback, NULL, 0); + + const char* params[] = {GUAC_VNC_ARGV_PASSWORD, NULL}; + + /* Send the request for password to the owner. */ + guac_client_owner_send_required(gc, params); + + /* Wait for the arguments to be returned */ + guac_argv_await(params); + + } + + return guac_strdup(settings->password); + } rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { - guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); - guac_vnc_settings* settings = ((guac_vnc_client*) gc->data)->settings; + guac_client* gc = rfbClientGetClientData(client, GUAC_VNC_CLIENT_KEY); + guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data); + guac_vnc_settings* settings = vnc_client->settings; + + /* Handle request for Username/Password credentials */ if (credentialType == rfbCredentialTypeUser) { rfbCredential *creds = malloc(sizeof(rfbCredential)); - creds->userCredential.username = settings->username; - creds->userCredential.password = settings->password; + + /* If the client supports the "required" instruction, prompt for and + update those. */ + if (guac_client_owner_supports_required(gc)) { + char* params[3] = {NULL}; + int i = 0; + + /* Check if username is not provided. */ + if (settings->username == NULL) { + guac_argv_register(GUAC_VNC_ARGV_USERNAME, guac_vnc_argv_callback, NULL, 0); + params[i] = GUAC_VNC_ARGV_USERNAME; + i++; + } + + /* Check if password is not provided. */ + if (settings->password == NULL) { + guac_argv_register(GUAC_VNC_ARGV_PASSWORD, guac_vnc_argv_callback, NULL, 0); + params[i] = GUAC_VNC_ARGV_PASSWORD; + i++; + } + + params[i] = NULL; + + /* If we have empty parameters, request them and await response. */ + if (i > 0) { + guac_client_owner_send_required(gc, (const char**) params); + guac_argv_await((const char**) params); + } + } + + /* Copy the values and return the credential set. */ + creds->userCredential.username = guac_strdup(settings->username); + creds->userCredential.password = guac_strdup(settings->password); return creds; + } guac_client_abort(gc, GUAC_PROTOCOL_STATUS_SERVER_ERROR, diff --git a/src/protocols/vnc/client.c b/src/protocols/vnc/client.c index cee1d4d5..93371ee6 100644 --- a/src/protocols/vnc/client.c +++ b/src/protocols/vnc/client.c @@ -50,8 +50,8 @@ int guac_client_init(guac_client* client) { client->data = vnc_client; #ifdef ENABLE_VNC_TLS_LOCKING - /* Initialize the write lock */ - pthread_mutex_init(&(vnc_client->tls_lock), NULL); + /* Initialize the TLS write lock */ + pthread_mutex_init(&vnc_client->tls_lock, NULL); #endif /* Init clipboard */ diff --git a/src/protocols/vnc/settings.c b/src/protocols/vnc/settings.c index c0d14859..c6f36464 100644 --- a/src/protocols/vnc/settings.c +++ b/src/protocols/vnc/settings.c @@ -19,6 +19,7 @@ #include "config.h" +#include "argv.h" #include "client.h" #include "common/defaults.h" #include "settings.h" @@ -37,8 +38,8 @@ const char* GUAC_VNC_CLIENT_ARGS[] = { "port", "read-only", "encodings", - "username", - "password", + GUAC_VNC_ARGV_USERNAME, + GUAC_VNC_ARGV_PASSWORD, "swap-red-blue", "color-depth", "cursor", @@ -392,11 +393,11 @@ guac_vnc_settings* guac_vnc_parse_args(guac_user* user, settings->username = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_USERNAME, ""); /* NOTE: freed by libvncclient */ + IDX_USERNAME, NULL); settings->password = guac_user_parse_args_string(user, GUAC_VNC_CLIENT_ARGS, argv, - IDX_PASSWORD, ""); /* NOTE: freed by libvncclient */ + IDX_PASSWORD, NULL); /* Remote cursor */ if (strcmp(argv[IDX_CURSOR], "remote") == 0) { @@ -624,8 +625,10 @@ void guac_vnc_settings_free(guac_vnc_settings* settings) { free(settings->clipboard_encoding); free(settings->encodings); free(settings->hostname); + free(settings->password); free(settings->recording_name); free(settings->recording_path); + free(settings->username); #ifdef ENABLE_VNC_REPEATER /* Free VNC repeater settings */ diff --git a/src/protocols/vnc/user.c b/src/protocols/vnc/user.c index a3d38d79..4e6f473c 100644 --- a/src/protocols/vnc/user.c +++ b/src/protocols/vnc/user.c @@ -32,6 +32,7 @@ #include "pulse/pulse.h" #endif +#include #include #include #include @@ -98,6 +99,10 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) { /* Inbound (client to server) clipboard transfer */ if (!settings->disable_paste) user->clipboard_handler = guac_vnc_clipboard_handler; + + /* Updates to connection parameters if we own the connection */ + if (user->owner) + user->argv_handler = guac_argv_handler; #ifdef ENABLE_COMMON_SSH /* Set generic (non-filesystem) file upload handler */ diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c index 31d97022..33bd5c59 100644 --- a/src/protocols/vnc/vnc.c +++ b/src/protocols/vnc/vnc.c @@ -259,9 +259,6 @@ void* guac_vnc_client_thread(void* data) { "clipboard encoding: '%s'.", settings->clipboard_encoding); } - /* Ensure connection is kept alive during lengthy connects */ - guac_socket_require_keep_alive(client->socket); - /* Set up libvncclient logging */ rfbClientLog = guac_vnc_client_log_info; rfbClientErr = guac_vnc_client_log_error;