GUACAMOLE-1283: Add synchronization around absolutely all outbound RDP messages.

The FreeRDP library is intended to be threadsafe, but is not reliably so
with respect to legacy RDP encryption and outbound messages. When
outbound messages are sent by multiple threads, the encryption key used
for legacy RDP encryption may not be updated correctly, resulting in a
fatal connection error like:

"ERRINFO_DECRYPT_FAILED (0x00001192):(a) Decryption using Standard RDP
Security mechanisms (section 5.3.6) failed. (b) Session key creation
using Standard RDP Security mechanisms (section 5.3.5) failed."
This commit is contained in:
Michael Jumper 2021-04-07 17:13:48 -07:00
parent b2ae2fdf00
commit 27e762d06f
15 changed files with 217 additions and 74 deletions

View File

@ -31,9 +31,10 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() { guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) {
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
pthread_mutex_init(&(buffer->lock), NULL); pthread_mutex_init(&(buffer->lock), NULL);
buffer->client = client;
return buffer; return buffer;
} }
@ -270,7 +271,7 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
/* Only actually invoke if defined */ /* Only actually invoke if defined */
if (audio_buffer->flush_handler) if (audio_buffer->flush_handler)
audio_buffer->flush_handler(audio_buffer->packet, audio_buffer->flush_handler(audio_buffer, audio_buffer->packet,
audio_buffer->bytes_written, audio_buffer->data); audio_buffer->bytes_written, audio_buffer->data);
/* Reset buffer in all cases */ /* Reset buffer in all cases */

View File

@ -24,10 +24,21 @@
#include <guacamole/user.h> #include <guacamole/user.h>
#include <pthread.h> #include <pthread.h>
/**
* A buffer of arbitrary audio data. Received audio data can be written to this
* buffer, and will automatically be flushed via a given handler once the
* internal buffer reaches capacity.
*/
typedef struct guac_rdp_audio_buffer guac_rdp_audio_buffer;
/** /**
* Handler which is invoked when a guac_rdp_audio_buffer's internal packet * Handler which is invoked when a guac_rdp_audio_buffer's internal packet
* buffer has reached capacity and must be flushed. * buffer has reached capacity and must be flushed.
* *
* @param audio_buffer
* The guac_rdp_audio_buffer that has reached capacity and is being
* flushed.
*
* @param buffer * @param buffer
* The buffer which needs to be flushed as an audio packet. * The buffer which needs to be flushed as an audio packet.
* *
@ -40,8 +51,8 @@
* The arbitrary data pointer provided when the audio buffer was * The arbitrary data pointer provided when the audio buffer was
* initialized. * initialized.
*/ */
typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length, typedef void guac_rdp_audio_buffer_flush_handler(guac_rdp_audio_buffer* audio_buffer,
void* data); char* buffer, int length, void* data);
/** /**
* A description of an arbitrary PCM audio format. * A description of an arbitrary PCM audio format.
@ -66,12 +77,7 @@ typedef struct guac_rdp_audio_format {
} guac_rdp_audio_format; } guac_rdp_audio_format;
/** struct guac_rdp_audio_buffer {
* A buffer of arbitrary audio data. Received audio data can be written to this
* buffer, and will automatically be flushed via a given handler once the
* internal buffer reaches capacity.
*/
typedef struct guac_rdp_audio_buffer {
/** /**
* Lock which is acquired/released to ensure accesses to the audio buffer * Lock which is acquired/released to ensure accesses to the audio buffer
@ -79,6 +85,11 @@ typedef struct guac_rdp_audio_buffer {
*/ */
pthread_mutex_t lock; pthread_mutex_t lock;
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/** /**
* The user from which this audio buffer will receive data. If no user has * The user from which this audio buffer will receive data. If no user has
* yet opened an associated audio stream, this will be NULL. * yet opened an associated audio stream, this will be NULL.
@ -145,17 +156,20 @@ typedef struct guac_rdp_audio_buffer {
*/ */
void* data; void* data;
} guac_rdp_audio_buffer; };
/** /**
* Allocates a new audio buffer. The new audio buffer will ignore any received * Allocates a new audio buffer. The new audio buffer will ignore any received
* data until guac_rdp_audio_buffer_begin() is invoked, and will resume * data until guac_rdp_audio_buffer_begin() is invoked, and will resume
* ignoring received data once guac_rdp_audio_buffer_end() is invoked. * ignoring received data once guac_rdp_audio_buffer_end() is invoked.
* *
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return * @return
* A newly-allocated audio buffer. * A newly-allocated audio buffer.
*/ */
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client);
/** /**
* Associates the given audio buffer with the underlying audio stream which * Associates the given audio buffer with the underlying audio stream which

View File

@ -76,6 +76,9 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* We support CP-1252 and UTF-16 text */ /* We support CP-1252 and UTF-16 text */
CLIPRDR_FORMAT_LIST format_list = { CLIPRDR_FORMAT_LIST format_list = {
.msgType = CB_FORMAT_LIST, .msgType = CB_FORMAT_LIST,
@ -86,10 +89,12 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
.numFormats = 2 .numFormats = 2
}; };
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format list");
"format list");
return cliprdr->ClientFormatList(cliprdr, &format_list); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientFormatList(cliprdr, &format_list);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -107,6 +112,17 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
*/ */
static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) { static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
/* This function is only invoked within FreeRDP-specific handlers for
* CLIPRDR, which are not assigned, and thus not callable, until after the
* relevant guac_rdp_clipboard structure is allocated and associated with
* the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* We support CP-1252 and UTF-16 text */
CLIPRDR_GENERAL_CAPABILITY_SET cap_set = { CLIPRDR_GENERAL_CAPABILITY_SET cap_set = {
.capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */ .capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */
.capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */ .capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */
@ -119,7 +135,11 @@ static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
.capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set .capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set
}; };
return cliprdr->ClientCapabilities(cliprdr, &caps); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientCapabilities(cliprdr, &caps);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -190,6 +210,9 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Create new data request */ /* Create new data request */
CLIPRDR_FORMAT_DATA_REQUEST data_request = { CLIPRDR_FORMAT_DATA_REQUEST data_request = {
.requestedFormatId = format .requestedFormatId = format
@ -199,11 +222,14 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
* data is received via a Format Data Response PDU */ * data is received via a Format Data Response PDU */
clipboard->requested_format = format; clipboard->requested_format = format;
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data request.");
"format data request.");
/* Send request */ /* Send request */
return cliprdr->ClientFormatDataRequest(cliprdr, &data_request); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientFormatDataRequest(cliprdr, &data_request);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -265,15 +291,19 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received " guac_client* client = clipboard->client;
"format list."); guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format list.");
CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = { CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {
.msgFlags = CB_RESPONSE_OK .msgFlags = CB_RESPONSE_OK
}; };
/* Report successful processing of format list */ /* Report successful processing of format list */
pthread_mutex_lock(&(rdp_client->message_lock));
cliprdr->ClientFormatListResponse(cliprdr, &format_list_response); cliprdr->ClientFormatListResponse(cliprdr, &format_list_response);
pthread_mutex_unlock(&(rdp_client->message_lock));
/* Prefer Unicode (in this case, UTF-16) */ /* Prefer Unicode (in this case, UTF-16) */
if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT)) if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT))
@ -284,9 +314,9 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT); return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT);
/* Ignore any unsupported data */ /* Ignore any unsupported data */
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring unsupported " guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring unsupported clipboard "
"clipboard data. Only Unicode and text clipboard formats are " "data. Only Unicode and text clipboard formats are currently "
"currently supported."); "supported.");
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
@ -320,8 +350,10 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received " guac_client* client = clipboard->client;
"format data request."); guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data request.");
guac_iconv_write* writer; guac_iconv_write* writer;
const char* input = clipboard->clipboard->buffer; const char* input = clipboard->clipboard->buffer;
@ -341,11 +373,11 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
/* Warn if clipboard data cannot be sent as intended due to a violation /* Warn if clipboard data cannot be sent as intended due to a violation
* of the CLIPRDR spec */ * of the CLIPRDR spec */
default: default:
guac_client_log(clipboard->client, GUAC_LOG_WARNING, "Received " guac_client_log(client, GUAC_LOG_WARNING, "Received clipboard "
"clipboard data cannot be sent to the RDP server because " "data cannot be sent to the RDP server because the RDP "
"the RDP server has requested a clipboard format which " "server has requested a clipboard format which was not "
"was not declared as available. This violates the " "declared as available. This violates the specification "
"specification for the CLIPRDR channel."); "for the CLIPRDR channel.");
free(output); free(output);
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
@ -363,10 +395,12 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
.msgFlags = CB_RESPONSE_OK .msgFlags = CB_RESPONSE_OK
}; };
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data response.");
"format data response.");
pthread_mutex_lock(&(rdp_client->message_lock));
UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response); UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response);
pthread_mutex_unlock(&(rdp_client->message_lock));
free(start); free(start);
return result; return result;
@ -407,9 +441,9 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
/* Ignore received data if copy has been disabled */ /* Ignore received data if copy has been disabled */
if (settings->disable_copy) { if (settings->disable_copy) {
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring received " guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring received clipboard "
"clipboard data as copying from within the remote desktop has " "data as copying from within the remote desktop has been "
"been explicitly disabled."); "explicitly disabled.");
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
} }
@ -439,9 +473,8 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
* here. The values which may be stored within requested_format are * here. The values which may be stored within requested_format are
* completely within our control. */ * completely within our control. */
default: default:
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Requested " guac_client_log(client, GUAC_LOG_DEBUG, "Requested clipboard data "
"clipboard data in unsupported format (0x%X).", "in unsupported format (0x%X).", clipboard->requested_format);
clipboard->requested_format);
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
} }
@ -453,7 +486,7 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
int length = strnlen(received_data, sizeof(received_data)); int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(clipboard->clipboard, "text/plain"); guac_common_clipboard_reset(clipboard->clipboard, "text/plain");
guac_common_clipboard_append(clipboard->clipboard, received_data, length); guac_common_clipboard_append(clipboard->clipboard, received_data, length);
guac_common_clipboard_send(clipboard->clipboard, clipboard->client); guac_common_clipboard_send(clipboard->clipboard, client);
} }
return CHANNEL_RC_OK; return CHANNEL_RC_OK;

View File

@ -89,12 +89,16 @@ void guac_rdp_common_svc_write(guac_rdp_common_svc* svc,
return; return;
} }
guac_rdp_client* rdp_client = (guac_rdp_client*) svc->client->data;
/* NOTE: The wStream sent via pVirtualChannelWriteEx will automatically be /* NOTE: The wStream sent via pVirtualChannelWriteEx will automatically be
* freed later with a call to Stream_Free() when handling the * freed later with a call to Stream_Free() when handling the
* corresponding write cancel/completion event. */ * corresponding write cancel/completion event. */
pthread_mutex_lock(&(rdp_client->message_lock));
svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle, svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle,
svc->_open_handle, Stream_Buffer(output_stream), svc->_open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream); Stream_GetPosition(output_stream), output_stream);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }

View File

@ -31,9 +31,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
guac_rdp_disp* guac_rdp_disp_alloc() { guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) {
guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp));
disp->client = client;
/* Not yet connected */ /* Not yet connected */
disp->disp = NULL; disp->disp = NULL;
@ -220,8 +221,17 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp,
}}; }};
/* Send display update notification if display channel is connected */ /* Send display update notification if display channel is connected */
if (disp->disp != NULL) if (disp->disp != NULL) {
guac_client* client = disp->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
pthread_mutex_lock(&(rdp_client->message_lock));
disp->disp->SendMonitorLayout(disp->disp, 1, monitors); disp->disp->SendMonitorLayout(disp->disp, 1, monitors);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
} }
} }

View File

@ -48,6 +48,11 @@
*/ */
typedef struct guac_rdp_disp { typedef struct guac_rdp_disp {
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/** /**
* Display control interface. * Display control interface.
*/ */
@ -81,10 +86,13 @@ typedef struct guac_rdp_disp {
* Allocates a new display update module, which will ultimately control the * Allocates a new display update module, which will ultimately control the
* display update channel once connected. * display update channel once connected.
* *
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return * @return
* A newly-allocated display update module. * A newly-allocated display update module.
*/ */
guac_rdp_disp* guac_rdp_disp_alloc(); guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client);
/** /**
* Frees the resources associated with support for the RDP Display Update * Frees the resources associated with support for the RDP Display Update

View File

@ -86,7 +86,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client handshake response */ /* Send client handshake response */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientHandshake(rail, &handshake); status = rail->ClientHandshake(rail, &handshake);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -95,7 +98,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client status */ /* Send client status */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientInformation(rail, &client_status); status = rail->ClientInformation(rail, &client_status);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -139,7 +145,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client system parameters */ /* Send client system parameters */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientSystemParam(rail, &sysparam); status = rail->ClientSystemParam(rail, &sysparam);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -151,7 +160,11 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Execute desired RemoteApp command */ /* Execute desired RemoteApp command */
return rail->ClientExecute(rail, &exec); pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientExecute(rail, &exec);
pthread_mutex_unlock(&(rdp_client->message_lock));
return status;
} }

View File

@ -32,9 +32,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
guac_rdp_rdpei* guac_rdp_rdpei_alloc() { guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) {
guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei)); guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei));
rdpei->client = client;
/* Not yet connected */ /* Not yet connected */
rdpei->rdpei = NULL; rdpei->rdpei = NULL;
@ -107,6 +108,9 @@ void guac_rdp_rdpei_load_plugin(rdpContext* context) {
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y, int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
double force) { double force) {
guac_client* client = rdpei->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int contact_id; /* Ignored */ int contact_id; /* Ignored */
/* Track touches only if channel is connected */ /* Track touches only if channel is connected */
@ -149,20 +153,31 @@ int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
if (!touch->active) if (!touch->active)
return 1; return 1;
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchEnd(context, id, x, y, &contact_id); context->TouchEnd(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 0; touch->active = 0;
} }
/* Signal the start of a touch if this is the first we've seen it */ /* Signal the start of a touch if this is the first we've seen it */
else if (!touch->active) { else if (!touch->active) {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchBegin(context, id, x, y, &contact_id); context->TouchBegin(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 1; touch->active = 1;
} }
/* Established touches need only be updated */ /* Established touches need only be updated */
else else {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchUpdate(context, id, x, y, &contact_id); context->TouchUpdate(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
return 0; return 0;

View File

@ -66,6 +66,11 @@ typedef struct guac_rdp_rdpei_touch {
*/ */
typedef struct guac_rdp_rdpei { typedef struct guac_rdp_rdpei {
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/** /**
* RDPEI control interface. * RDPEI control interface.
*/ */
@ -83,10 +88,13 @@ typedef struct guac_rdp_rdpei {
* channel once connected. The RDPEI channel allows multi-touch input * channel once connected. The RDPEI channel allows multi-touch input
* events to be sent to the RDP server. * events to be sent to the RDP server.
* *
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return * @return
* A newly-allocated RDPEI module. * A newly-allocated RDPEI module.
*/ */
guac_rdp_rdpei* guac_rdp_rdpei_alloc(); guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client);
/** /**
* Frees the resources associated with support for the RDPEI channel. Only * Frees the resources associated with support for the RDPEI channel. Only

View File

@ -145,10 +145,10 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
rdp_client->clipboard = guac_rdp_clipboard_alloc(client); rdp_client->clipboard = guac_rdp_clipboard_alloc(client);
/* Init display update module */ /* Init display update module */
rdp_client->disp = guac_rdp_disp_alloc(); rdp_client->disp = guac_rdp_disp_alloc(client);
/* Init multi-touch support module (RDPEI) */ /* Init multi-touch support module (RDPEI) */
rdp_client->rdpei = guac_rdp_rdpei_alloc(); rdp_client->rdpei = guac_rdp_rdpei_alloc(client);
/* Redirect FreeRDP log messages to guac_client_log() */ /* Redirect FreeRDP log messages to guac_client_log() */
guac_rdp_redirect_wlog(client); guac_rdp_redirect_wlog(client);
@ -158,8 +158,9 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
pthread_mutexattr_settype(&(rdp_client->attributes), pthread_mutexattr_settype(&(rdp_client->attributes),
PTHREAD_MUTEX_RECURSIVE); PTHREAD_MUTEX_RECURSIVE);
/* Initalize the lock */ /* Init required locks */
pthread_rwlock_init(&(rdp_client->lock), NULL); pthread_rwlock_init(&(rdp_client->lock), NULL);
pthread_mutex_init(&(rdp_client->message_lock), &(rdp_client->attributes));
/* Set handlers */ /* Set handlers */
client->join_handler = guac_rdp_user_join_handler; client->join_handler = guac_rdp_user_join_handler;
@ -226,6 +227,7 @@ int guac_rdp_client_free_handler(guac_client* client) {
guac_rdp_audio_buffer_free(rdp_client->audio_input); guac_rdp_audio_buffer_free(rdp_client->audio_input);
pthread_rwlock_destroy(&(rdp_client->lock)); pthread_rwlock_destroy(&(rdp_client->lock));
pthread_mutex_destroy(&(rdp_client->message_lock));
/* Free client data */ /* Free client data */
free(rdp_client); free(rdp_client);

View File

@ -54,8 +54,11 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
guac_common_recording_report_mouse(rdp_client->recording, x, y, mask); guac_common_recording_report_mouse(rdp_client->recording, x, y, mask);
/* If button mask unchanged, just send move event */ /* If button mask unchanged, just send move event */
if (mask == rdp_client->mouse_button_mask) if (mask == rdp_client->mouse_button_mask) {
pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y); rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_MOVE, x, y);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
/* Otherwise, send events describing button change */ /* Otherwise, send events describing button change */
else { else {
@ -75,7 +78,9 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3; if (released_mask & 0x02) flags |= PTR_FLAGS_BUTTON3;
if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2; if (released_mask & 0x04) flags |= PTR_FLAGS_BUTTON2;
pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }
@ -91,7 +96,9 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88; if (pressed_mask & 0x10) flags |= PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88;
/* Send event */ /* Send event */
pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y); rdp_inst->input->MouseEvent(rdp_inst->input, flags, x, y);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }
@ -99,18 +106,18 @@ int guac_rdp_user_mouse_handler(guac_user* user, int x, int y, int mask) {
if (pressed_mask & 0x18) { if (pressed_mask & 0x18) {
/* Down */ /* Down */
if (pressed_mask & 0x08) if (pressed_mask & 0x08) {
rdp_inst->input->MouseEvent( pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input, rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_WHEEL | 0x78, x, y);
PTR_FLAGS_WHEEL | 0x78, pthread_mutex_unlock(&(rdp_client->message_lock));
x, y); }
/* Up */ /* Up */
if (pressed_mask & 0x10) if (pressed_mask & 0x10) {
rdp_inst->input->MouseEvent( pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input, rdp_inst->input->MouseEvent(rdp_inst->input, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, x, y);
PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | 0x88, pthread_mutex_unlock(&(rdp_client->message_lock));
x, y); }
} }

View File

@ -104,8 +104,9 @@ static void guac_rdp_send_key_event(guac_rdp_client* rdp_client,
return; return;
/* Send actual key */ /* Send actual key */
rdp_inst->input->KeyboardEvent(rdp_inst->input, pthread_mutex_lock(&(rdp_client->message_lock));
flags | pressed_flags, scancode); rdp_inst->input->KeyboardEvent(rdp_inst->input, flags | pressed_flags, scancode);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }
@ -132,9 +133,9 @@ static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client,
return; return;
/* Send Unicode event */ /* Send Unicode event */
rdp_inst->input->UnicodeKeyboardEvent( pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input, rdp_inst->input->UnicodeKeyboardEvent(rdp_inst->input, 0, codepoint);
0, codepoint); pthread_mutex_unlock(&(rdp_client->message_lock));
} }
@ -161,7 +162,9 @@ static void guac_rdp_send_synchronize_event(guac_rdp_client* rdp_client,
return; return;
/* Synchronize lock key states */ /* Synchronize lock key states */
pthread_mutex_lock(&(rdp_client->message_lock));
rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags); rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }

View File

@ -247,6 +247,8 @@ static void guac_rdp_ai_send_formatchange(IWTSVirtualChannel* channel,
void guac_rdp_ai_process_version(guac_client* client, void guac_rdp_ai_process_version(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) { IWTSVirtualChannel* channel, wStream* stream) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Verify we have at least 4 bytes available (UINT32) */ /* Verify we have at least 4 bytes available (UINT32) */
if (Stream_GetRemainingLength(stream) < 4) { if (Stream_GetRemainingLength(stream) < 4) {
guac_client_log(client, GUAC_LOG_WARNING, "Audio input Versoin PDU " guac_client_log(client, GUAC_LOG_WARNING, "Audio input Versoin PDU "
@ -269,8 +271,9 @@ void guac_rdp_ai_process_version(guac_client* client,
Stream_Write_UINT32(response, 1); /* Version */ Stream_Write_UINT32(response, 1); /* Version */
/* Send response */ /* Send response */
channel->Write(channel, (UINT32) Stream_GetPosition(response), pthread_mutex_lock(&(rdp_client->message_lock));
Stream_Buffer(response), NULL); channel->Write(channel, (UINT32) Stream_GetPosition(response), Stream_Buffer(response), NULL);
pthread_mutex_unlock(&(rdp_client->message_lock));
Stream_Free(response, TRUE); Stream_Free(response, TRUE);
} }
@ -313,26 +316,35 @@ void guac_rdp_ai_process_formats(guac_client* client,
format.channels, format.bps / 8); format.channels, format.bps / 8);
/* Accept single format */ /* Accept single format */
pthread_mutex_lock(&(rdp_client->message_lock));
guac_rdp_ai_send_incoming_data(channel); guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_formats(channel, &format, 1); guac_rdp_ai_send_formats(channel, &format, 1);
pthread_mutex_unlock(&(rdp_client->message_lock));
return; return;
} }
/* No formats available */ /* No formats available */
guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format."); guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format.");
pthread_mutex_lock(&(rdp_client->message_lock));
guac_rdp_ai_send_incoming_data(channel); guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_formats(channel, NULL, 0); guac_rdp_ai_send_formats(channel, NULL, 0);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }
void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) { void guac_rdp_ai_flush_packet(guac_rdp_audio_buffer* audio_buffer,
char* buffer, int length, void* data) {
guac_client* client = audio_buffer->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data; IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data;
/* Send data over channel */ /* Send data over channel */
pthread_mutex_lock(&(rdp_client->message_lock));
guac_rdp_ai_send_incoming_data(channel); guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_data(channel, buffer, length); guac_rdp_ai_send_data(channel, buffer, length);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }
@ -363,8 +375,10 @@ void guac_rdp_ai_process_open(guac_client* client,
audio_buffer->out_format.bps); audio_buffer->out_format.bps);
/* Success */ /* Success */
pthread_mutex_lock(&(rdp_client->message_lock));
guac_rdp_ai_send_formatchange(channel, initial_format); guac_rdp_ai_send_formatchange(channel, initial_format);
guac_rdp_ai_send_open_reply(channel, 0); guac_rdp_ai_send_open_reply(channel, 0);
pthread_mutex_unlock(&(rdp_client->message_lock));
/* Begin receiving audio data */ /* Begin receiving audio data */
guac_rdp_audio_buffer_begin(audio_buffer, packet_frames, guac_rdp_audio_buffer_begin(audio_buffer, packet_frames,

View File

@ -107,7 +107,7 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
/* Load "AUDIO_INPUT" plugin for audio input*/ /* Load "AUDIO_INPUT" plugin for audio input*/
if (settings->enable_audio_input) { if (settings->enable_audio_input) {
rdp_client->audio_input = guac_rdp_audio_buffer_alloc(); rdp_client->audio_input = guac_rdp_audio_buffer_alloc(client);
guac_rdp_audio_load_plugin(instance->context); guac_rdp_audio_load_plugin(instance->context);
} }
@ -564,13 +564,16 @@ static int guac_rdp_handle_connection(guac_client* client) {
guac_timestamp frame_end; guac_timestamp frame_end;
int frame_remaining; int frame_remaining;
/* Check the libfreerdp fds */ /* Handle any queued FreeRDP events (this may result in RDP
if (!freerdp_check_event_handles(rdp_inst->context)) { * messages being sent) */
pthread_mutex_lock(&(rdp_client->message_lock));
int event_result = freerdp_check_event_handles(rdp_inst->context);
pthread_mutex_unlock(&(rdp_client->message_lock));
/* Flag connection failure */ /* Abort if FreeRDP event handling fails */
if (!event_result) {
wait_result = -1; wait_result = -1;
break; break;
} }
/* Calculate time remaining in frame */ /* Calculate time remaining in frame */
@ -634,7 +637,9 @@ static int guac_rdp_handle_connection(guac_client* client) {
} }
/* Disconnect client and channels */ /* Disconnect client and channels */
pthread_mutex_lock(&(rdp_client->message_lock));
freerdp_disconnect(rdp_inst); freerdp_disconnect(rdp_inst);
pthread_mutex_unlock(&(rdp_client->message_lock));
/* Clean up FreeRDP internal GDI implementation */ /* Clean up FreeRDP internal GDI implementation */
gdi_free(rdp_inst); gdi_free(rdp_inst);

View File

@ -172,6 +172,12 @@ typedef struct guac_rdp_client {
*/ */
pthread_rwlock_t lock; pthread_rwlock_t lock;
/**
* Lock which synchronizes the sending of each RDP message, ensuring
* attempts to send RDP messages never overlap.
*/
pthread_mutex_t message_lock;
} guac_rdp_client; } guac_rdp_client;
/** /**