From b64b8f375a4c79bf2092ae4042042a78a2db7b35 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Oct 2019 17:01:26 -0700 Subject: [PATCH] GUACAMOLE-249: Restore support for CLIPRDR channel. --- src/protocols/rdp/Makefile.am | 4 +- src/protocols/rdp/client.c | 6 +- src/protocols/rdp/clipboard.c | 527 ++++++++++++++++++++++++++++++++ src/protocols/rdp/clipboard.h | 144 +++++++++ src/protocols/rdp/rdp.c | 10 +- src/protocols/rdp/rdp.h | 13 +- src/protocols/rdp/rdp_cliprdr.c | 249 --------------- src/protocols/rdp/rdp_cliprdr.h | 110 ------- src/protocols/rdp/rdp_stream.c | 64 ---- src/protocols/rdp/rdp_stream.h | 22 +- 10 files changed, 684 insertions(+), 465 deletions(-) create mode 100644 src/protocols/rdp/clipboard.c create mode 100644 src/protocols/rdp/clipboard.h delete mode 100644 src/protocols/rdp/rdp_cliprdr.c delete mode 100644 src/protocols/rdp/rdp_cliprdr.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index c22f5288..b8f0ae28 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -36,6 +36,7 @@ libguac_client_rdp_la_SOURCES = \ audio_input.c \ channels.c \ client.c \ + clipboard.c \ decompose.c \ dvc.c \ error.c \ @@ -44,7 +45,6 @@ libguac_client_rdp_la_SOURCES = \ ptr_string.c \ rdp.c \ rdp_bitmap.c \ - rdp_cliprdr.c \ rdp_color.c \ rdp_disp.c \ rdp_fs.c \ @@ -104,6 +104,7 @@ noinst_HEADERS = \ guac_svc/svc_service.h \ audio_input.h \ client.h \ + clipboard.h \ channels.h \ decompose.h \ dvc.h \ @@ -113,7 +114,6 @@ noinst_HEADERS = \ ptr_string.h \ rdp.h \ rdp_bitmap.h \ - rdp_cliprdr.h \ rdp_color.h \ rdp_disp.h \ rdp_fs.h \ diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index e2f8a4c2..d99ce8b4 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -56,7 +56,7 @@ int guac_client_init(guac_client* client, int argc, char** argv) { client->data = rdp_client; /* Init clipboard */ - rdp_client->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); + rdp_client->clipboard = guac_rdp_clipboard_alloc(client); /* Init display update module */ rdp_client->disp = guac_rdp_disp_alloc(); @@ -92,6 +92,9 @@ int guac_rdp_client_free_handler(guac_client* client) { if (rdp_client->settings != NULL) guac_rdp_settings_free(rdp_client->settings); + /* Clean up clipboard */ + guac_rdp_clipboard_free(rdp_client->clipboard); + /* Free display update module */ guac_rdp_disp_free(rdp_client->disp); @@ -128,7 +131,6 @@ int guac_rdp_client_free_handler(guac_client* client) { guac_rdp_audio_buffer_free(rdp_client->audio_input); /* Free client data */ - guac_common_clipboard_free(rdp_client->clipboard); free(rdp_client); return 0; diff --git a/src/protocols/rdp/clipboard.c b/src/protocols/rdp/clipboard.c new file mode 100644 index 00000000..b543b276 --- /dev/null +++ b/src/protocols/rdp/clipboard.c @@ -0,0 +1,527 @@ +/* + * 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 "channels.h" +#include "client.h" +#include "clipboard.h" +#include "common/clipboard.h" +#include "common/iconv.h" +#include "rdp.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * Sends a Format List PDU to the RDP server containing the formats of + * clipboard data supported. This PDU is used both to indicate the general + * clipboard formats supported at the begining of an RDP session and to inform + * the RDP server that new clipboard data is available within the listed + * formats. + * + * @param cliprdr + * The CliprdrClientContext structure used by FreeRDP to handle the + * CLIPRDR channel for the current RDP session. + * + * @return + * CHANNEL_RC_OK (zero) if the Format List PDU was sent successfully, an + * error code (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) { + + /* We support CP-1252 and UTF-16 text */ + CLIPRDR_FORMAT_LIST format_list = { + .formats = (CLIPRDR_FORMAT[]) { + { .formatId = CF_TEXT }, + { .formatId = CF_UNICODETEXT } + }, + .numFormats = 2, + .msgFlags = CB_RESPONSE_OK + }; + + return cliprdr->ClientFormatList(cliprdr, &format_list); + +} + +/** + * Callback invoked by the FreeRDP CLIPRDR plugin for received Monitor Ready + * PDUs. The Monitor Ready PDU is sent by the RDP server only during + * initialization of the CLIPRDR channel. It is part of the CLIPRDR channel + * handshake and indicates that the RDP server's handling of clipboard + * redirection is ready to proceed. + * + * @param cliprdr + * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR + * channel for the current RDP session. + * + * @param monitor_ready + * The CLIPRDR_MONITOR_READY structure representing the Monitor Ready PDU + * that was received. + * + * @return + * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code + * (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_monitor_ready(CliprdrClientContext* cliprdr, + const CLIPRDR_MONITOR_READY* monitor_ready) { + + /* Respond with supported format list */ + return guac_rdp_cliprdr_send_format_list(cliprdr); + +} + +/** + * Sends a Format Data Request PDU to the RDP server, requesting that available + * clipboard data be sent to the client in the specified format. This PDU is + * sent when the server indicating that clipboard data is available via a + * Format List PDU. + * + * @param client + * The guac_client associated with the current RDP session. + * + * @param format + * The clipboard format to request. This format must be one of the + * documented values used by the CLIPRDR channel for clipboard format IDs. + * + * @return + * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code + * (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_send_format_data_request( + CliprdrClientContext* cliprdr, UINT32 format) { + + /* FreeRDP-specific handlers for CLIPRDR 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); + + /* Create new data request */ + CLIPRDR_FORMAT_DATA_REQUEST data_request = { + .requestedFormatId = format + }; + + /* Note the format we've requested for reference later when the requested + * data is received via a Format Data Response PDU */ + clipboard->requested_format = format; + + /* Send request */ + return cliprdr->ClientFormatDataRequest(cliprdr, &data_request); + +} + +/** + * Returns whether the given Format List PDU indicates support for the given + * clipboard format. + * + * @param format_list + * The CLIPRDR_FORMAT_LIST structure representing the Format List PDU + * being tested. + * + * @param format_id + * The ID of the clipboard format to test, such as CF_TEXT or + * CF_UNICODETEXT. + * + * @return + * Non-zero if the given Format List PDU indicates support for the given + * clipboard format, zero otherwise. + */ +static int guac_rdp_cliprdr_format_supported(const CLIPRDR_FORMAT_LIST* format_list, + UINT format_id) { + + /* Search format list for matching ID */ + for (int i = 0; i < format_list->numFormats; i++) { + if (format_list->formats[i].formatId == format_id) + return 1; + } + + /* If no matching ID, format is not supported */ + return 0; + +} + +/** + * Callback invoked by the FreeRDP CLIPRDR plugin for received Format List + * PDUs. The Format List PDU is sent by the RDP server to indicate that new + * clipboard data has been copied and is available for retrieval in the formats + * listed. A client wishing to retrieve that data responds with a Format Data + * Request PDU. + * + * @param cliprdr + * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR + * channel for the current RDP session. + * + * @param format_list + * The CLIPRDR_FORMAT_LIST structure representing the Format List PDU that + * was received. + * + * @return + * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code + * (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_LIST* format_list) { + + /* FreeRDP-specific handlers for CLIPRDR 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); + + /* Prefer Unicode (in this case, UTF-16) */ + if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT)) + return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_UNICODETEXT); + + /* Use Windows' CP-1252 if Unicode unavailable */ + if (guac_rdp_cliprdr_format_supported(format_list, CF_TEXT)) + return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT); + + /* Ignore any unsupported data */ + guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring unsupported " + "clipboard data. Only Unicode and text clipboard formats are " + "currently supported."); + return CHANNEL_RC_OK; + +} + +/** + * Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data + * Request PDUs. The Format Data Request PDU is sent by the RDP server when + * requesting that clipboard data be sent, in response to a received Format + * List PDU. The client is required to respond with a Format Data Response PDU + * containing the requested data. + * + * @param cliprdr + * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR + * channel for the current RDP session. + * + * @param format_data_request + * The CLIPRDR_FORMAT_DATA_REQUEST structure representing the Format Data + * Request PDU that was received. + * + * @return + * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code + * (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_DATA_REQUEST* format_data_request) { + + /* FreeRDP-specific handlers for CLIPRDR 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_iconv_write* writer; + const char* input = clipboard->clipboard->buffer; + char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); + + /* Map requested clipboard format to a guac_iconv writer */ + switch (format_data_request->requestedFormatId) { + + case CF_TEXT: + writer = GUAC_WRITE_CP1252; + break; + + case CF_UNICODETEXT: + writer = GUAC_WRITE_UTF16; + break; + + /* Warn if clipboard data cannot be sent as intended due to a violation + * of the CLIPRDR spec */ + default: + guac_client_log(clipboard->client, GUAC_LOG_WARNING, "Received " + "clipboard data cannot be sent to the RDP server because " + "the RDP server has requested a clipboard format which " + "was not declared as available. This violates the " + "specification for the CLIPRDR channel."); + free(output); + return CHANNEL_RC_OK; + + } + + /* Send received clipboard data to the RDP server in the format + * requested */ + BYTE* start = (BYTE*) output; + guac_iconv(GUAC_READ_UTF8, &input, clipboard->clipboard->length, + writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH); + + CLIPRDR_FORMAT_DATA_RESPONSE data_response = { + .requestedFormatData = (BYTE*) output, + .dataLen = ((BYTE*) output) - start + }; + + return cliprdr->ClientFormatDataResponse(cliprdr, &data_response); + +} + +/** + * Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data + * Response PDUs. The Format Data Response PDU is sent by the RDP server when + * fullfilling a request for clipboard data received via a Format Data Request + * PDU. + * + * @param cliprdr + * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR + * channel for the current RDP session. + * + * @param format_data_response + * The CLIPRDR_FORMAT_DATA_RESPONSE structure representing the Format Data + * Response PDU that was received. + * + * @return + * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code + * (non-zero) otherwise. + */ +static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_DATA_RESPONSE* format_data_response) { + + /* FreeRDP-specific handlers for CLIPRDR 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); + + char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH]; + + guac_iconv_read* reader; + const char* input = (char*) format_data_response->requestedFormatData; + char* output = received_data; + + /* Find correct source encoding */ + switch (clipboard->requested_format) { + + /* Non-Unicode (Windows CP-1252) */ + case CF_TEXT: + reader = GUAC_READ_CP1252; + break; + + /* Unicode (UTF-16) */ + case CF_UNICODETEXT: + reader = GUAC_READ_UTF16; + break; + + /* If the format ID stored within the guac_rdp_clipboard structure is actually + * not supported here, then something has been implemented incorrectly. + * Either incorrect values are (somehow) being stored, or support for + * the format indicated by that value is incomplete and must be added + * here. The values which may be stored within requested_format are + * completely within our control. */ + default: + guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Requested " + "clipboard data in unsupported format (0x%X).", + clipboard->requested_format); + return CHANNEL_RC_OK; + + } + + /* Convert, store, and forward the clipboard data received from RDP + * server */ + if (guac_iconv(reader, &input, format_data_response->dataLen, + GUAC_WRITE_UTF8, &output, sizeof(received_data))) { + int length = strnlen(received_data, sizeof(received_data)); + guac_common_clipboard_reset(clipboard->clipboard, "text/plain"); + guac_common_clipboard_append(clipboard->clipboard, received_data, length); + guac_common_clipboard_send(clipboard->clipboard, clipboard->client); + } + + return CHANNEL_RC_OK; + +} + +/** + * Callback which associates handlers specific to Guacamole with the + * CliprdrClientContext instance allocated by FreeRDP to deal with received + * CLIPRDR (clipboard redirection) messages. + * + * This function is called whenever a channel connects via the PubSub event + * system within FreeRDP, but only has any effect if the connected channel is + * the CLIPRDR channel. This specific callback is registered with the PubSub + * system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is + * called. + * + * @param context + * The rdpContext associated with the active RDP session. + * + * @param e + * Event-specific arguments, mainly the name of the channel, and a + * reference to the associated plugin loaded for that channel by FreeRDP. + */ +static void guac_rdp_cliprdr_channel_connected(rdpContext* context, + ChannelConnectedEventArgs* e) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_rdp_clipboard* clipboard = rdp_client->clipboard; + + /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not + * callable, until after the relevant guac_rdp_clipboard structure is + * allocated and associated with the guac_rdp_client */ + assert(clipboard != NULL); + + /* Ignore connection event if it's not for the CLIPRDR channel */ + if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0) + return; + + /* The structure pointed to by pInterface is guaranteed to be a + * CliprdrClientContext if the channel is CLIPRDR */ + CliprdrClientContext* cliprdr = (CliprdrClientContext*) e->pInterface; + + /* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with + * eachother */ + cliprdr->custom = clipboard; + clipboard->cliprdr = cliprdr; + + cliprdr->MonitorReady = guac_rdp_cliprdr_monitor_ready; + cliprdr->ServerFormatList = guac_rdp_cliprdr_format_list; + cliprdr->ServerFormatDataRequest = guac_rdp_cliprdr_format_data_request; + cliprdr->ServerFormatDataResponse = guac_rdp_cliprdr_format_data_response; + + guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) " + "channel connected."); + +} + +guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) { + + /* Allocate clipboard and underlying storage */ + guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard)); + clipboard->client = client; + clipboard->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); + clipboard->requested_format = CF_TEXT; + + return clipboard; + +} + +void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard, + rdpContext* context) { + + /* Attempt to load FreeRDP support for the CLIPRDR channel */ + if (guac_freerdp_channels_load_plugin(context->channels, context->settings, "cliprdr", NULL)) { + guac_client_log(clipboard->client, GUAC_LOG_WARNING, + "Support for the CLIPRDR channel (clipboard redirection) " + "could not be loaded. This support normally takes the form of " + "a plugin which is built into FreeRDP. Lacking this support, " + "clipboard will not work."); + return; + } + + /* Complete RDP side of initialization when channel is connected */ + PubSub_SubscribeChannelConnected(context->pubSub, + (pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected); + + guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR " + "(clipboard redirection) registered. Awaiting channel " + "connection."); + +} + +void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard) { + + /* Do nothing if the clipboard is not actually allocated */ + if (clipboard == NULL) + return; + + /* Free clipboard and underlying storage */ + guac_common_clipboard_free(clipboard->clipboard); + free(clipboard); + +} + +int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Ignore stream creation if no clipboard structure is available to handle + * received data */ + guac_rdp_clipboard* clipboard = rdp_client->clipboard; + if (clipboard == NULL) + return 0; + + /* Handle any future "blob" and "end" instructions for this stream with + * handlers that are aware of the RDP clipboard state */ + stream->blob_handler = guac_rdp_clipboard_blob_handler; + stream->end_handler = guac_rdp_clipboard_end_handler; + + /* Clear any current contents, assigning the mimetype the data which will + * be received */ + guac_common_clipboard_reset(clipboard->clipboard, mimetype); + return 0; + +} + +int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Ignore received data if no clipboard structure is available to handle + * that data */ + guac_rdp_clipboard* clipboard = rdp_client->clipboard; + if (clipboard == NULL) + return 0; + + /* Append received data to current clipboard contents */ + guac_common_clipboard_append(clipboard->clipboard, (char*) data, length); + return 0; + +} + + +int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Ignore end of stream if no clipboard structure is available to handle + * the data that was received */ + guac_rdp_clipboard* clipboard = rdp_client->clipboard; + if (clipboard == NULL) + return 0; + + /* Terminate clipboard data with NULL */ + guac_common_clipboard_append(clipboard->clipboard, "", 1); + + /* Notify RDP server of new data, if connected */ + if (clipboard->cliprdr != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data received. " + "Reporting availability of clipboard data to RDP server."); + guac_rdp_cliprdr_send_format_list(clipboard->cliprdr); + } + else + guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data has been " + "received, but cannot be sent to the RDP server because the " + "CLIPRDR channel is not yet connected."); + + return 0; + +} + diff --git a/src/protocols/rdp/clipboard.h b/src/protocols/rdp/clipboard.h new file mode 100644 index 00000000..2562f12e --- /dev/null +++ b/src/protocols/rdp/clipboard.h @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_RDP_CLIPBOARD_H +#define GUAC_RDP_CLIPBOARD_H + +#include "config.h" +#include "common/clipboard.h" + +#include +#include +#include + +/** + * RDP clipboard, leveraging the "CLIPRDR" channel. + */ +typedef struct guac_rdp_clipboard { + + /** + * The guac_client associated with the RDP connection. The broadcast + * socket of this client will receive any clipboard data received from the + * RDP server. + */ + guac_client* client; + + /** + * CLIPRDR control interface. + */ + CliprdrClientContext* cliprdr; + + /** + * The current clipboard contents. + */ + guac_common_clipboard* clipboard; + + /** + * The format of the clipboard which was requested. Data received from + * the RDP server should conform to this format. This will be one of + * several legal clipboard format values defined within FreeRDP's WinPR + * library, such as CF_TEXT. + */ + UINT requested_format; + +} guac_rdp_clipboard; + +/** + * Allocates a new guac_rdp_clipboard which has been initialized for processing + * of Guacamole clipboard data. Support for the RDP side of the clipboard (the + * CLIPRDR channel) must be loaded separately during FreeRDP's PreConnect event + * using guac_rdp_clipboard_load_plugin(). + * + * @param client + * The guac_client associated with the Guacamole side of the RDP + * connection. + * + * @return + * A newly-allocated instance of guac_rdp_clipboard which has been + * initialized for processing Guacamole clipboard data. + */ +guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client); + +/** + * Initializes clipboard support for RDP and handling of the CLIPRDR channel. + * If failures occur, messages noting the specifics of those failures will be + * logged, and the RDP side of clipboard support will not be functional. + * + * This MUST be called within the PreConnect callback of the freerdp instance + * for CLIPRDR support to be loaded. + * + * @param clipboard + * The guac_rdp_clipboard instance which has been allocated for the current + * RDP connection. + * + * @param rdpContext + * The rdpContext associated with the FreeRDP side of the RDP connection. + */ +void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard, + rdpContext* context); + +/** + * Frees the resources associated with clipboard support for RDP and handling + * of the CLIPRDR channel. Only resources specific to Guacamole are freed. + * Resources specific to FreeRDP's handling of the CLIPRDR channel will be + * freed by FreeRDP. If the provided guac_rdp_clipboard is NULL, this function + * has no effect. + * + * @param clipboard + * The guac_rdp_clipboard instance which was been allocated for the current + * RDP connection. + */ +void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard); + +/** + * Handler for inbound clipboard data, received via the stream created by an + * inbound "clipboard" instruction. This handler will assign the + * stream-specific handlers for processing "blob" and "end" instructions which + * will eventually be received as clipboard data is sent. This specific handler + * is expected to be assigned to the guac_user object of any user that may send + * clipboard data. The guac_rdp_clipboard instance which will receive this data + * MUST already be stored on the guac_rdp_client structure associated with the + * current RDP connection. + */ +guac_user_clipboard_handler guac_rdp_clipboard_handler; + +/** + * Handler for stream data related to clipboard, received via "blob" + * instructions for a stream which has already been created with an inbound + * "clipboard" instruction. This specific handler is assigned to the + * guac_stream structure associated with that clipboard stream by + * guac_rdp_clipboard_handler(). The guac_rdp_clipboard instance which will + * receive this data MUST already be stored on the guac_rdp_client structure + * associated with the current RDP connection. + */ +guac_user_blob_handler guac_rdp_clipboard_blob_handler; + +/** + * Handler for end-of-stream related to clipboard, indicated via an "end" + * instruction for a stream which has already been created with an inbound + * "clipboard" instruction. This specific handler is assigned to the + * guac_stream structure associated with that clipboard stream by + * guac_rdp_clipboard_handler(). The guac_rdp_clipboard instance which will + * receive this data MUST already be stored on the guac_rdp_client structure + * associated with the current RDP connection. + */ +guac_user_end_handler guac_rdp_clipboard_end_handler; + +#endif + diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index ad490b8b..1497517d 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -22,6 +22,7 @@ #include "audio_input.h" #include "channels.h" #include "client.h" +#include "clipboard.h" #include "common/cursor.h" #include "common/display.h" #include "common/recording.h" @@ -30,7 +31,6 @@ #include "keyboard.h" #include "rdp.h" #include "rdp_bitmap.h" -#include "rdp_cliprdr.h" #include "rdp_disp.h" #include "rdp_fs.h" #include "rdp_print_job.h" @@ -109,11 +109,8 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { guac_rdp_audio_load_plugin(instance->context, dvc_list); } - /* Load clipboard plugin */ - if (guac_freerdp_channels_load_plugin(channels, instance->settings, - "cliprdr", NULL)) - guac_client_log(client, GUAC_LOG_WARNING, - "Failed to load cliprdr plugin. Clipboard will not work."); + /* Load "cliprdr" plugin for clipboard support */ + guac_rdp_clipboard_load_plugin(rdp_client->clipboard, context); /* If RDPSND/RDPDR required, load them */ if (settings->printing_enabled @@ -414,7 +411,6 @@ static int guac_rdp_handle_connection(guac_client* client) { rdp_client->current_surface = rdp_client->display->default_surface; - rdp_client->requested_clipboard_format = CF_TEXT; rdp_client->available_svc = guac_common_list_alloc(); /* Init client */ diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 5d181a18..910e6853 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -23,6 +23,7 @@ #include "config.h" #include "audio_input.h" +#include "clipboard.h" #include "common/clipboard.h" #include "common/display.h" #include "common/list.h" @@ -95,17 +96,9 @@ typedef struct guac_rdp_client { guac_rdp_keyboard* keyboard; /** - * The current clipboard contents. + * The current state of the clipboard and the CLIPRDR channel. */ - guac_common_clipboard* clipboard; - - /** - * The format of the clipboard which was requested. Data received from - * the RDP server should conform to this format. This will be one of - * several legal clipboard format values defined within FreeRDP, such as - * CB_FORMAT_TEXT. - */ - int requested_clipboard_format; + guac_rdp_clipboard* clipboard; /** * Audio output, if any. diff --git a/src/protocols/rdp/rdp_cliprdr.c b/src/protocols/rdp/rdp_cliprdr.c deleted file mode 100644 index 52960e01..00000000 --- a/src/protocols/rdp/rdp_cliprdr.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include "client.h" -#include "common/clipboard.h" -#include "common/iconv.h" -#include "rdp.h" -#include "rdp_cliprdr.h" - -#include -#include -#include -#include -#include - -#include -#include - -void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event) { -#if 0 - switch (event->event_type) { - - case CliprdrChannel_MonitorReady: - guac_rdp_process_cb_monitor_ready(client, event); - break; - - case CliprdrChannel_FormatList: - guac_rdp_process_cb_format_list(client, - (RDP_CB_FORMAT_LIST_EVENT*) event); - break; - - case CliprdrChannel_DataRequest: - guac_rdp_process_cb_data_request(client, - (RDP_CB_DATA_REQUEST_EVENT*) event); - break; - - case CliprdrChannel_DataResponse: - guac_rdp_process_cb_data_response(client, - (RDP_CB_DATA_RESPONSE_EVENT*) event); - break; - - default: - guac_client_log(client, GUAC_LOG_INFO, - "Unknown cliprdr event type: 0x%x", - GetMessageType(event->id)); - - } -#endif -} - -void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event) { -#if 0 - rdpChannels* channels = - ((guac_rdp_client*) client->data)->rdp_inst->context->channels; - - RDP_CB_FORMAT_LIST_EVENT* format_list = - (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( - CliprdrChannel_Class, - CliprdrChannel_FormatList, - NULL, NULL); - - /* Received notification of clipboard support. */ - - /* Respond with supported format list */ - format_list->formats = (UINT32*) malloc(sizeof(UINT32)*2); - format_list->formats[0] = CF_TEXT; - format_list->formats[1] = CF_UNICODETEXT; - format_list->num_formats = 2; - - freerdp_channels_send_event(channels, (wMessage*) format_list); -#endif -} - -/** - * Sends a clipboard data request for the given format. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param format - * The clipboard format to request. This format must be one of the - * documented values used by the CLIPRDR channel for clipboard format IDs. - */ -static void __guac_rdp_cb_request_format(guac_client* client, int format) { -#if 0 - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - rdpChannels* channels = rdp_client->rdp_inst->context->channels; - - /* Create new data request */ - RDP_CB_DATA_REQUEST_EVENT* data_request = - (RDP_CB_DATA_REQUEST_EVENT*) freerdp_event_new( - CliprdrChannel_Class, - CliprdrChannel_DataRequest, - NULL, NULL); - - /* Set to requested format */ - rdp_client->requested_clipboard_format = format; - data_request->format = format; - - /* Send request */ - freerdp_channels_send_event(channels, (wMessage*) data_request); -#endif -} - -void guac_rdp_process_cb_format_list(guac_client* client, - RDP_CB_FORMAT_LIST_EVENT* event) { - - int formats = 0; - - /* Received notification of available data */ - - int i; - for (i=0; inum_formats; i++) { - - /* If plain text available, request it */ - if (event->formats[i] == CF_TEXT) - formats |= GUAC_RDP_CLIPBOARD_FORMAT_CP1252; - else if (event->formats[i] == CF_UNICODETEXT) - formats |= GUAC_RDP_CLIPBOARD_FORMAT_UTF16; - - } - - /* Prefer Unicode to plain text */ - if (formats & GUAC_RDP_CLIPBOARD_FORMAT_UTF16) { - __guac_rdp_cb_request_format(client, CF_UNICODETEXT); - return; - } - - /* Use plain text if Unicode unavailable */ - if (formats & GUAC_RDP_CLIPBOARD_FORMAT_CP1252) { - __guac_rdp_cb_request_format(client, CF_TEXT); - return; - } - - /* Ignore if no supported format available */ - guac_client_log(client, GUAC_LOG_INFO, "Ignoring unsupported clipboard data"); - -} - -void guac_rdp_process_cb_data_request(guac_client* client, - RDP_CB_DATA_REQUEST_EVENT* event) { -#if 0 - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - rdpChannels* channels = rdp_client->rdp_inst->context->channels; - - guac_iconv_write* writer; - const char* input = rdp_client->clipboard->buffer; - char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); - - RDP_CB_DATA_RESPONSE_EVENT* data_response; - - /* Determine output encoding */ - switch (event->format) { - - case CF_TEXT: - writer = GUAC_WRITE_CP1252; - break; - - case CF_UNICODETEXT: - writer = GUAC_WRITE_UTF16; - break; - - default: - guac_client_log(client, GUAC_LOG_ERROR, - "Server requested unsupported clipboard data type"); - free(output); - return; - - } - - /* Create new data response */ - data_response = (RDP_CB_DATA_RESPONSE_EVENT*) freerdp_event_new( - CliprdrChannel_Class, - CliprdrChannel_DataResponse, - NULL, NULL); - - /* Set data and size */ - data_response->data = (BYTE*) output; - guac_iconv(GUAC_READ_UTF8, &input, rdp_client->clipboard->length, - writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH); - data_response->size = ((BYTE*) output) - data_response->data; - - /* Send response */ - freerdp_channels_send_event(channels, (wMessage*) data_response); -#endif -} - -void guac_rdp_process_cb_data_response(guac_client* client, - RDP_CB_DATA_RESPONSE_EVENT* event) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH]; - - guac_iconv_read* reader; - const char* input = (char*) event->data; - char* output = received_data; - - /* Find correct source encoding */ - switch (rdp_client->requested_clipboard_format) { - - /* Non-Unicode */ - case CF_TEXT: - reader = GUAC_READ_CP1252; - break; - - /* Unicode (UTF-16) */ - case CF_UNICODETEXT: - reader = GUAC_READ_UTF16; - break; - - default: - guac_client_log(client, GUAC_LOG_ERROR, "Requested clipboard data in " - "unsupported format %i", - rdp_client->requested_clipboard_format); - return; - - } - - /* Convert send clipboard data */ - if (guac_iconv(reader, &input, event->size, - GUAC_WRITE_UTF8, &output, sizeof(received_data))) { - - int length = strnlen(received_data, sizeof(received_data)); - guac_common_clipboard_reset(rdp_client->clipboard, "text/plain"); - guac_common_clipboard_append(rdp_client->clipboard, received_data, length); - guac_common_clipboard_send(rdp_client->clipboard, client); - - } - -} - diff --git a/src/protocols/rdp/rdp_cliprdr.h b/src/protocols/rdp/rdp_cliprdr.h deleted file mode 100644 index b279d5f8..00000000 --- a/src/protocols/rdp/rdp_cliprdr.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -#ifndef __GUAC_RDP_RDP_CLIPRDR_H -#define __GUAC_RDP_RDP_CLIPRDR_H - -#include "config.h" - -#include -#include -#include - -/** - * Clipboard format for text encoded in Windows CP1252. - */ -#define GUAC_RDP_CLIPBOARD_FORMAT_CP1252 1 - -/** - * Clipboard format for text encoded in UTF-16. - */ -#define GUAC_RDP_CLIPBOARD_FORMAT_UTF16 2 - -/** - * Called within the main RDP connection thread whenever a CLIPRDR message is - * received. This function will dispatch that message to an appropriate - * function, specific to that message type. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message. - */ -void guac_rdp_process_cliprdr_event(guac_client* client, wMessage* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Monitor Ready event. It - * is the responsibility of this function to respond to the Monitor Ready - * event with a list of supported clipboard formats. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Monitor Ready event. - */ -void guac_rdp_process_cb_monitor_ready(guac_client* client, wMessage* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Format List event. It - * is the responsibility of this function to respond to the Format List - * event with a request for clipboard data in one of the enumerated formats. - * This event is fired whenever remote clipboard data is available. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Format List event. - */ -void guac_rdp_process_cb_format_list(guac_client* client, - RDP_CB_FORMAT_LIST_EVENT* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Data Request event. It - * is the responsibility of this function to respond to the Data Request - * event with a data response containing the current clipoard contents. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Data Request event. - */ -void guac_rdp_process_cb_data_request(guac_client* client, - RDP_CB_DATA_REQUEST_EVENT* event); - -/** - * Handles the given CLIPRDR event, which MUST be a Data Response event. It - * is the responsibility of this function to read and forward the received - * clipboard data to connected clients. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param event - * The received CLIPRDR message, which must be a Data Response event. - */ -void guac_rdp_process_cb_data_response(guac_client* client, - RDP_CB_DATA_RESPONSE_EVENT* event); - -#endif - diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c index 1e1d4708..3d33ea20 100644 --- a/src/protocols/rdp/rdp_stream.c +++ b/src/protocols/rdp/rdp_stream.c @@ -20,7 +20,6 @@ #include "config.h" #include "client.h" -#include "common/clipboard.h" #include "rdp.h" #include "rdp_fs.h" #if 0 @@ -153,24 +152,6 @@ int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, } -int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, - char* mimetype) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_rdp_stream* rdp_stream; - - /* Init stream data */ - stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); - stream->blob_handler = guac_rdp_clipboard_blob_handler; - stream->end_handler = guac_rdp_clipboard_end_handler; - rdp_stream->type = GUAC_RDP_INBOUND_CLIPBOARD_STREAM; - - guac_common_clipboard_reset(rdp_client->clipboard, mimetype); - return 0; - -} - int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { @@ -237,16 +218,6 @@ int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, } -int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, - void* data, int length) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_clipboard_append(rdp_client->clipboard, (char*) data, length); - - return 0; -} - int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { guac_client* client = user->client; @@ -275,41 +246,6 @@ int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { } -int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - /* Terminate clipboard data with NULL */ - guac_common_clipboard_append(rdp_client->clipboard, "", 1); - - /* Notify RDP server of new data, if connected */ -#if 0 - freerdp* rdp_inst = rdp_client->rdp_inst; - if (rdp_inst != NULL) { - - rdpChannels* channels = rdp_inst->context->channels; - - RDP_CB_FORMAT_LIST_EVENT* format_list = - (RDP_CB_FORMAT_LIST_EVENT*) freerdp_event_new( - CliprdrChannel_Class, - CliprdrChannel_FormatList, - NULL, NULL); - - /* Notify server that text data is now available */ - format_list->formats = (UINT32*) malloc(sizeof(UINT32) * 2); - format_list->formats[0] = CF_TEXT; - format_list->formats[1] = CF_UNICODETEXT; - format_list->num_formats = 2; - - freerdp_channels_send_event(channels, (wMessage*) format_list); - - } -#endif - - return 0; -} - int guac_rdp_download_ack_handler(guac_user* user, guac_stream* stream, char* message, guac_protocol_status status) { diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h index 427e091f..6aaa1b97 100644 --- a/src/protocols/rdp/rdp_stream.h +++ b/src/protocols/rdp/rdp_stream.h @@ -118,12 +118,7 @@ typedef enum guac_rdp_stream_type { /** * The inbound half of a static virtual channel. */ - GUAC_RDP_INBOUND_SVC_STREAM, - - /** - * An inbound stream of clipboard data. - */ - GUAC_RDP_INBOUND_CLIPBOARD_STREAM + GUAC_RDP_INBOUND_SVC_STREAM } guac_rdp_stream_type; @@ -171,11 +166,6 @@ guac_user_file_handler guac_rdp_upload_file_handler; */ guac_user_pipe_handler guac_rdp_svc_pipe_handler; -/** - * Handler for inbound clipboard data. - */ -guac_user_clipboard_handler guac_rdp_clipboard_handler; - /** * Handler for stream data related to file uploads. */ @@ -186,21 +176,11 @@ guac_user_blob_handler guac_rdp_upload_blob_handler; */ guac_user_blob_handler guac_rdp_svc_blob_handler; -/** - * Handler for stream data related to clipboard. - */ -guac_user_blob_handler guac_rdp_clipboard_blob_handler; - /** * Handler for end-of-stream related to file uploads. */ guac_user_end_handler guac_rdp_upload_end_handler; -/** - * Handler for end-of-stream related to clipboard. - */ -guac_user_end_handler guac_rdp_clipboard_end_handler; - /** * Handler for acknowledgements of receipt of data related to file downloads. */