GUACAMOLE-221: Implement guacd support for verifying that client can accept the required instruction.

This commit is contained in:
Nick Couchman 2020-06-28 15:56:01 -04:00
parent b00b629b96
commit bc8ed4e104
15 changed files with 269 additions and 27 deletions

View File

@ -341,6 +341,9 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
} }
/* Enable keep alive on the broadcast socket */
guac_socket_require_keep_alive(client->socket);
cleanup_client: cleanup_client:
/* Request client to stop/disconnect */ /* Request client to stop/disconnect */

View File

@ -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 guac_client_owner_send_required(guac_client* client, const char** required) {
int retval; /* Don't send required instruction if client does not support it. */
if (!guac_client_owner_supports_required(client))
return -1;
pthread_rwlock_rdlock(&(client->__users_lock)); return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_send_required_callback, required));
/* Invoke callback with current owner */
retval = guac_protocol_send_required(client->__owner->socket, required);
/* Flush the socket */
guac_socket_flush(client->__owner->socket);
pthread_rwlock_unlock(&(client->__users_lock));
/* Return value from sending required */
return retval;
} }
@ -652,6 +669,44 @@ static void* __webp_support_callback(guac_user* user, void* data) {
} }
#endif #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) { int guac_client_supports_webp(guac_client* client) {
#ifdef ENABLE_WEBP #ifdef ENABLE_WEBP

View File

@ -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, guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface, int quality, int lossless); 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 * 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. * not support WebP, or the server cannot encode WebP images, zero is returned.

View File

@ -38,7 +38,7 @@
* This version is passed by the __guac_protocol_send_args() function from the * This version is passed by the __guac_protocol_send_args() function from the
* server to the client during the client/server handshake. * 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 * The maximum number of bytes that should be sent in any one blob instruction

View File

@ -20,6 +20,8 @@
#ifndef _GUAC_PROTOCOL_TYPES_H #ifndef _GUAC_PROTOCOL_TYPES_H
#define _GUAC_PROTOCOL_TYPES_H #define _GUAC_PROTOCOL_TYPES_H
#include <stddef.h>
/** /**
* Type definitions related to the Guacamole protocol. * 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_ROUND = 0x2
} guac_line_join_style; } 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 #endif

View File

@ -1023,5 +1023,32 @@ int guac_protocol_send_name(guac_socket* socket, const char* name);
*/ */
int guac_protocol_decode_base64(char* base64); 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 #endif

View File

@ -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. * to ensure neither side of the socket times out while the socket is open.
* This ping will take the form of a "nop" instruction. * 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 * @param socket
* The guac_socket to declare as requiring an automatic keep-alive ping. * The guac_socket to declare as requiring an automatic keep-alive ping.
*/ */

View File

@ -96,6 +96,12 @@ struct guac_user_info {
*/ */
const char* timezone; 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;
}; };
struct guac_user { struct guac_user {
@ -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, guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface, int quality, int lossless); 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 * Returns whether the given user supports WebP. If the user does not
* support WebP, or the server cannot encode WebP images, zero is returned. * support WebP, or the server cannot encode WebP images, zero is returned.

View File

@ -23,6 +23,7 @@
#include "guacamole/layer.h" #include "guacamole/layer.h"
#include "guacamole/object.h" #include "guacamole/object.h"
#include "guacamole/protocol.h" #include "guacamole/protocol.h"
#include "guacamole/protocol-types.h"
#include "guacamole/socket.h" #include "guacamole/socket.h"
#include "guacamole/stream.h" #include "guacamole/stream.h"
#include "guacamole/unicode.h" #include "guacamole/unicode.h"
@ -39,6 +40,34 @@
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
/**
* 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 */ /* Output formatting functions */
ssize_t __guac_socket_write_length_string(guac_socket* socket, const char* str) { 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;
}

View File

@ -150,10 +150,8 @@ guac_socket* guac_socket_alloc() {
socket->state = GUAC_SOCKET_OPEN; socket->state = GUAC_SOCKET_OPEN;
socket->last_write_timestamp = guac_timestamp_current(); socket->last_write_timestamp = guac_timestamp_current();
/* keep alive ping by default */ /* No keep alive ping by default */
socket->__keep_alive_enabled = 1; socket->__keep_alive_enabled = 0;
pthread_create(&(socket->__keep_alive_thread), NULL,
__guac_socket_keep_alive_thread, (void*) socket);
/* No handlers yet */ /* No handlers yet */
socket->read_handler = NULL; socket->read_handler = NULL;

View File

@ -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 " guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection "
"\"%s\" (%i users now present)", user->user_id, "\"%s\" (%i users now present)", user->user_id,
client->connection_id, client->connected_users); 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 " guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol "
"version \"%s\"", parser->argv[0]); "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 " guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined "
"its protocol version."); "its protocol version.");
user->info.protocol_version = GUAC_PROTOCOL_VERSION_1_0_0;
}
/* Handle user I/O, wait for connection to terminate */ /* Handle user I/O, wait for connection to terminate */
guac_user_start(parser, user, usec_timeout); guac_user_start(parser, user, usec_timeout);

View File

@ -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) { int guac_user_supports_webp(guac_user* user) {
#ifdef ENABLE_WEBP #ifdef ENABLE_WEBP

View File

@ -202,7 +202,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
/** /**
* Callback invoked by FreeRDP when authentication is required but a username * Callback invoked by FreeRDP when authentication is required but a username
* and password has not already been given. In the case of Guacamole, this * 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. * username/password must be given within the connection parameters.
* *
* @param instance * @param instance
@ -232,6 +232,11 @@ static BOOL rdp_freerdp_authenticate(freerdp* instance, char** username,
char* params[4] = {}; char* params[4] = {};
int i = 0; 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) { if (settings->username == NULL) {
params[i] = GUAC_RDP_PARAMETER_NAME_USERNAME; params[i] = GUAC_RDP_PARAMETER_NAME_USERNAME;
rdp_client->rdp_credential_flags |= GUAC_RDP_CRED_FLAG_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. */ /* Lock the client thread. */
pthread_mutex_lock(&(rdp_client->rdp_credential_lock)); 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); guac_client_owner_send_required(client, (const char**) params);
/* Wait for condition. */ /* Wait for condition. */

View File

@ -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; 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. */ /* Lock the terminal thread while prompting for the credential. */
pthread_mutex_lock(&(ssh_client->term_channel_lock)); pthread_mutex_lock(&(ssh_client->term_channel_lock));

View File

@ -36,6 +36,11 @@ char* guac_vnc_get_password(rfbClient* client) {
guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data); guac_vnc_client* vnc_client = ((guac_vnc_client*) gc->data);
guac_vnc_settings* settings = vnc_client->settings; 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 password isn't around, prompt for it. */
if (settings->password == NULL || strcmp(settings->password, "") == 0) { if (settings->password == NULL || strcmp(settings->password, "") == 0) {
/* Lock the thread. */ /* Lock the thread. */
@ -68,6 +73,15 @@ rfbCredential* guac_vnc_get_credentials(rfbClient* client, int credentialType) {
/* Handle request for Username/Password credentials */ /* Handle request for Username/Password credentials */
if (credentialType == rfbCredentialTypeUser) { if (credentialType == rfbCredentialTypeUser) {
rfbCredential *creds = malloc(sizeof(rfbCredential)); 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}; char* params[2] = {NULL};
int i = 0; int i = 0;