From bc8ed4e10471f63431ca30922d762921fdef50fa Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Sun, 28 Jun 2020 15:56:01 -0400 Subject: [PATCH] GUACAMOLE-221: Implement guacd support for verifying that client can accept the required instruction. --- src/guacd/proc.c | 3 + src/libguac/client.c | 81 ++++++++++++++++++---- src/libguac/guacamole/client.h | 15 ++++ src/libguac/guacamole/protocol-constants.h | 2 +- src/libguac/guacamole/protocol-types.h | 35 ++++++++++ src/libguac/guacamole/protocol.h | 27 ++++++++ src/libguac/guacamole/socket.h | 4 -- src/libguac/guacamole/user.h | 17 +++++ src/libguac/protocol.c | 60 ++++++++++++++++ src/libguac/socket.c | 6 +- src/libguac/user-handshake.c | 8 ++- src/libguac/user.c | 9 +++ src/protocols/rdp/rdp.c | 9 ++- src/protocols/ssh/ssh.c | 4 ++ src/protocols/vnc/auth.c | 16 ++++- 15 files changed, 269 insertions(+), 27 deletions(-) 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 6f1c595b..cc898371 100644 --- a/src/libguac/client.c +++ b/src/libguac/client.c @@ -478,22 +478,39 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) { } +/** + * 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 + * Pointer to a NULL-terminated array of required parameters that will be + * passed on to the owner to continue the connection. + * + * @return + * A pointer to an integer containing the return status of the send + * operation. + */ +static void* guac_client_owner_send_required_callback(guac_user* user, void* data) { + + const char** required = (const char **) data; + + /* Send required parameters to owner. */ + return (void*) ((intptr_t) guac_protocol_send_required(user->socket, required)); + +} + int guac_client_owner_send_required(guac_client* client, const char** required) { - int retval; - - pthread_rwlock_rdlock(&(client->__users_lock)); - - /* Invoke callback with current owner */ - retval = guac_protocol_send_required(client->__owner->socket, required); + /* Don't send required instruction if client does not support it. */ + if (!guac_client_owner_supports_required(client)) + return -1; - /* Flush the socket */ - guac_socket_flush(client->__owner->socket); - - pthread_rwlock_unlock(&(client->__users_lock)); - - /* Return value from sending required */ - return retval; + return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_send_required_callback, required)); } @@ -652,6 +669,44 @@ static void* __webp_support_callback(guac_user* user, void* data) { } #endif +/** + * Callback function which is invoked by guac_client_owner_supports_required() + * to determine if the owner of a client supports the "required" instruction, + * updating the flag to indicate support. + * + * @param user + * The guac_user that will be checked for "required" instruction support. + * + * @param data + * Pointer to an int containing the status for support of the "required" + * instruction. This will be 0 if the owner does not support this + * instruction, or 1 if the owner does support it. + * + * @return + * Always NULL. + */ +static void* guac_owner_supports_required_callback(guac_user* user, void* data) { + + int* required_supported = (int *) data; + + /* Check if owner supports required */ + if (*required_supported) + *required_supported = guac_user_supports_required(user); + + return NULL; + +} + +int guac_client_owner_supports_required(guac_client* client) { + + int required_supported = 1; + + guac_client_for_owner(client, guac_owner_supports_required_callback, &required_supported); + + return required_supported; + +} + 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 578fc593..4ffe5cf4 100644 --- a/src/libguac/guacamole/client.h +++ b/src/libguac/guacamole/client.h @@ -708,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..46ca4573 100644 --- a/src/libguac/guacamole/protocol-types.h +++ b/src/libguac/guacamole/protocol-types.h @@ -20,6 +20,8 @@ #ifndef _GUAC_PROTOCOL_TYPES_H #define _GUAC_PROTOCOL_TYPES_H +#include + /** * Type definitions related to the Guacamole protocol. * @@ -276,5 +278,38 @@ typedef enum guac_line_join_style { GUAC_LINE_JOIN_ROUND = 0x2 } guac_line_join_style; +/** + * Track the various protocol versions supported by guacd to help negotiate + * features that may not be supported by various versions of the client. + */ +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. + */ + 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 6ee1ad4e..c4c6f211 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -1023,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, search the mapping + * to find the matching enum version, or GUAC_PROTOCOL_VERSION_UNKNOWN if no + * match is found. + * + * @param version_string + * The string representation of the protocol version. + * + * @return + * The enum value of the protocol version, or GUAC_PROTOCOL_VERSION_UNKNOWN + * if no match is found. + */ +guac_protocol_version guac_protocol_string_to_version(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/socket.h b/src/libguac/guacamole/socket.h index cce4f69e..2da697fe 100644 --- a/src/libguac/guacamole/socket.h +++ b/src/libguac/guacamole/socket.h @@ -136,10 +136,6 @@ void guac_socket_free(guac_socket* socket); * to ensure neither side of the socket times out while the socket is open. * This ping will take the form of a "nop" instruction. * - * @deprecated - * Manually starting the keep alive process on sockets is no longer - * necessary, as the sockets enable the "nop" ping by default. - * * @param socket * The guac_socket to declare as requiring an automatic keep-alive ping. */ 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 ff903c4b..2c1e132b 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 +/** + * Structure mapping the enum value of a protocol version to the string + * representation of the version. + */ +typedef struct guac_protocol_version_mapping { + + /** + * The enum value representing the selected 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 version enum 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) { @@ -1275,3 +1304,34 @@ int guac_protocol_decode_base64(char* base64) { } +guac_protocol_version guac_protocol_string_to_version(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) { + + if (current->version == version) { + return (const char*) current->version_string; + } + + } + + return NULL; + +} + diff --git a/src/libguac/socket.c b/src/libguac/socket.c index 8892256e..66442d0c 100644 --- a/src/libguac/socket.c +++ b/src/libguac/socket.c @@ -150,10 +150,8 @@ guac_socket* guac_socket_alloc() { socket->state = GUAC_SOCKET_OPEN; socket->last_write_timestamp = guac_timestamp_current(); - /* keep alive ping by default */ - socket->__keep_alive_enabled = 1; - pthread_create(&(socket->__keep_alive_thread), NULL, - __guac_socket_keep_alive_thread, (void*) socket); + /* No keep alive ping by default */ + socket->__keep_alive_enabled = 0; /* No handlers yet */ socket->read_handler = NULL; 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/rdp.c b/src/protocols/rdp/rdp.c index c4bfc28b..cfba7f50 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -202,7 +202,7 @@ 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 + * function always succeeds but does not populate the username or password. The * username/password must be given within the connection parameters. * * @param instance @@ -232,6 +232,11 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, char* params[4] = {}; int i = 0; + /* If the client does not support the "required" instruction, just + exit. */ + if (!guac_client_owner_supports_required(client)) + return TRUE; + if (settings->username == NULL) { params[i] = GUAC_RDP_PARAMETER_NAME_USERNAME; rdp_client->rdp_credential_flags |= GUAC_RDP_CRED_FLAG_USERNAME; @@ -257,7 +262,7 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username, /* Lock the client thread. */ pthread_mutex_lock(&(rdp_client->rdp_credential_lock)); - /* Send require parameters to the owner. */ + /* Send required parameters to the owner. */ guac_client_owner_send_required(client, (const char**) params); /* Wait for condition. */ diff --git a/src/protocols/ssh/ssh.c b/src/protocols/ssh/ssh.c index 9c87380f..40040c01 100644 --- a/src/protocols/ssh/ssh.c +++ b/src/protocols/ssh/ssh.c @@ -74,6 +74,10 @@ static void guac_ssh_get_credential(guac_client *client, char* cred_name) { guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + /* If the client does not support the "required" instruction, just return. */ + if (!guac_client_owner_supports_required(client)) + return; + /* Lock the terminal thread while prompting for the credential. */ pthread_mutex_lock(&(ssh_client->term_channel_lock)); diff --git a/src/protocols/vnc/auth.c b/src/protocols/vnc/auth.c index fc534573..260acee3 100644 --- a/src/protocols/vnc/auth.c +++ b/src/protocols/vnc/auth.c @@ -36,6 +36,11 @@ char* guac_vnc_get_password(rfbClient* client) { 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_vnc_client*) gc->data)->settings->password; + /* If password isn't around, prompt for it. */ if (settings->password == NULL || strcmp(settings->password, "") == 0) { /* Lock the thread. */ @@ -64,10 +69,19 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) { 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)); + + /* If the client does not support the "required" instruction, just return + the parameters already configured. */ + if (!guac_client_owner_supports_required(gc)) { + creds->userCredential.username = settings->username; + creds->userCredential.password = settings->password; + return creds; + } + char* params[2] = {NULL}; int i = 0;