cliprdr.c (27173B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "channels/cliprdr.h" 21#include "client.h" 22#include "common/clipboard.h" 23#include "common/iconv.h" 24#include "config.h" 25#include "plugins/channels.h" 26#include "rdp.h" 27 28#include <freerdp/client/cliprdr.h> 29#include <freerdp/event.h> 30#include <freerdp/freerdp.h> 31#include <guacamole/client.h> 32#include <guacamole/mem.h> 33#include <guacamole/stream.h> 34#include <guacamole/user.h> 35#include <winpr/wtsapi.h> 36#include <winpr/wtypes.h> 37 38#include <assert.h> 39#include <stdlib.h> 40#include <string.h> 41 42#ifdef FREERDP_CLIPRDR_CALLBACKS_REQUIRE_CONST 43/** 44 * FreeRDP 2.0.0-rc4 and newer requires the final argument for all CLIPRDR 45 * callbacks to be const. 46 */ 47#define CLIPRDR_CONST const 48#else 49/** 50 * FreeRDP 2.0.0-rc3 and older requires the final argument for all CLIPRDR 51 * callbacks to NOT be const. 52 */ 53#define CLIPRDR_CONST 54#endif 55 56/** 57 * Sends a Format List PDU to the RDP server containing the formats of 58 * clipboard data supported. This PDU is used both to indicate the general 59 * clipboard formats supported at the begining of an RDP session and to inform 60 * the RDP server that new clipboard data is available within the listed 61 * formats. 62 * 63 * @param cliprdr 64 * The CliprdrClientContext structure used by FreeRDP to handle the 65 * CLIPRDR channel for the current RDP session. 66 * 67 * @return 68 * CHANNEL_RC_OK (zero) if the Format List PDU was sent successfully, an 69 * error code (non-zero) otherwise. 70 */ 71static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) { 72 73 /* This function is only invoked within FreeRDP-specific handlers for 74 * CLIPRDR, which are not assigned, and thus not callable, until after the 75 * relevant guac_rdp_clipboard structure is allocated and associated with 76 * the CliprdrClientContext */ 77 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 78 assert(clipboard != NULL); 79 80 guac_client* client = clipboard->client; 81 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 82 83 /* We support CP-1252 and UTF-16 text */ 84 CLIPRDR_FORMAT_LIST format_list = { 85 .msgType = CB_FORMAT_LIST, 86 .formats = (CLIPRDR_FORMAT[]) { 87 { .formatId = CF_TEXT }, 88 { .formatId = CF_UNICODETEXT } 89 }, 90 .numFormats = 2 91 }; 92 93 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format list"); 94 95 pthread_mutex_lock(&(rdp_client->message_lock)); 96 int retval = cliprdr->ClientFormatList(cliprdr, &format_list); 97 pthread_mutex_unlock(&(rdp_client->message_lock)); 98 return retval; 99 100} 101 102/** 103 * Sends a Clipboard Capabilities PDU to the RDP server describing the features 104 * of the CLIPRDR channel that are supported by the client. 105 * 106 * @param cliprdr 107 * The CliprdrClientContext structure used by FreeRDP to handle the 108 * CLIPRDR channel for the current RDP session. 109 * 110 * @return 111 * CHANNEL_RC_OK (zero) if the Clipboard Capabilities PDU was sent 112 * successfully, an error code (non-zero) otherwise. 113 */ 114static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) { 115 116 /* This function is only invoked within FreeRDP-specific handlers for 117 * CLIPRDR, which are not assigned, and thus not callable, until after the 118 * relevant guac_rdp_clipboard structure is allocated and associated with 119 * the CliprdrClientContext */ 120 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 121 assert(clipboard != NULL); 122 123 guac_client* client = clipboard->client; 124 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 125 126 /* We support CP-1252 and UTF-16 text */ 127 CLIPRDR_GENERAL_CAPABILITY_SET cap_set = { 128 .capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */ 129 .capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */ 130 .version = CB_CAPS_VERSION_2, /* The version of the CLIPRDR specification supported */ 131 .generalFlags = CB_USE_LONG_FORMAT_NAMES /* Bitwise OR of all supported feature flags */ 132 }; 133 134 CLIPRDR_CAPABILITIES caps = { 135 .cCapabilitiesSets = 1, 136 .capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set 137 }; 138 139 pthread_mutex_lock(&(rdp_client->message_lock)); 140 int retval = cliprdr->ClientCapabilities(cliprdr, &caps); 141 pthread_mutex_unlock(&(rdp_client->message_lock)); 142 143 return retval; 144 145} 146 147/** 148 * Callback invoked by the FreeRDP CLIPRDR plugin for received Monitor Ready 149 * PDUs. The Monitor Ready PDU is sent by the RDP server only during 150 * initialization of the CLIPRDR channel. It is part of the CLIPRDR channel 151 * handshake and indicates that the RDP server's handling of clipboard 152 * redirection is ready to proceed. 153 * 154 * @param cliprdr 155 * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR 156 * channel for the current RDP session. 157 * 158 * @param monitor_ready 159 * The CLIPRDR_MONITOR_READY structure representing the Monitor Ready PDU 160 * that was received. 161 * 162 * @return 163 * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code 164 * (non-zero) otherwise. 165 */ 166static UINT guac_rdp_cliprdr_monitor_ready(CliprdrClientContext* cliprdr, 167 CLIPRDR_CONST CLIPRDR_MONITOR_READY* monitor_ready) { 168 169 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 170 * callable, until after the relevant guac_rdp_clipboard structure is 171 * allocated and associated with the CliprdrClientContext */ 172 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 173 assert(clipboard != NULL); 174 175 guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received " 176 "monitor ready."); 177 178 /* Respond with capabilities ... */ 179 int status = guac_rdp_cliprdr_send_capabilities(cliprdr); 180 if (status != CHANNEL_RC_OK) 181 return status; 182 183 /* ... and supported format list */ 184 return guac_rdp_cliprdr_send_format_list(cliprdr); 185 186} 187 188/** 189 * Sends a Format Data Request PDU to the RDP server, requesting that available 190 * clipboard data be sent to the client in the specified format. This PDU is 191 * sent when the server indicates that clipboard data is available via a Format 192 * List PDU. 193 * 194 * @param client 195 * The guac_client associated with the current RDP session. 196 * 197 * @param format 198 * The clipboard format to request. This format must be one of the 199 * documented values used by the CLIPRDR channel for clipboard format IDs. 200 * 201 * @return 202 * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code 203 * (non-zero) otherwise. 204 */ 205static UINT guac_rdp_cliprdr_send_format_data_request( 206 CliprdrClientContext* cliprdr, UINT32 format) { 207 208 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 209 * callable, until after the relevant guac_rdp_clipboard structure is 210 * allocated and associated with the CliprdrClientContext */ 211 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 212 assert(clipboard != NULL); 213 214 guac_client* client = clipboard->client; 215 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 216 217 /* Create new data request */ 218 CLIPRDR_FORMAT_DATA_REQUEST data_request = { 219 .requestedFormatId = format 220 }; 221 222 /* Note the format we've requested for reference later when the requested 223 * data is received via a Format Data Response PDU */ 224 clipboard->requested_format = format; 225 226 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data request."); 227 228 /* Send request */ 229 pthread_mutex_lock(&(rdp_client->message_lock)); 230 int retval = cliprdr->ClientFormatDataRequest(cliprdr, &data_request); 231 pthread_mutex_unlock(&(rdp_client->message_lock)); 232 233 return retval; 234 235} 236 237/** 238 * Returns whether the given Format List PDU indicates support for the given 239 * clipboard format. 240 * 241 * @param format_list 242 * The CLIPRDR_FORMAT_LIST structure representing the Format List PDU 243 * being tested. 244 * 245 * @param format_id 246 * The ID of the clipboard format to test, such as CF_TEXT or 247 * CF_UNICODETEXT. 248 * 249 * @return 250 * Non-zero if the given Format List PDU indicates support for the given 251 * clipboard format, zero otherwise. 252 */ 253static int guac_rdp_cliprdr_format_supported(const CLIPRDR_FORMAT_LIST* format_list, 254 UINT format_id) { 255 256 /* Search format list for matching ID */ 257 for (int i = 0; i < format_list->numFormats; i++) { 258 if (format_list->formats[i].formatId == format_id) 259 return 1; 260 } 261 262 /* If no matching ID, format is not supported */ 263 return 0; 264 265} 266 267/** 268 * Callback invoked by the FreeRDP CLIPRDR plugin for received Format List 269 * PDUs. The Format List PDU is sent by the RDP server to indicate that new 270 * clipboard data has been copied and is available for retrieval in the formats 271 * listed. A client wishing to retrieve that data responds with a Format Data 272 * Request PDU. 273 * 274 * @param cliprdr 275 * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR 276 * channel for the current RDP session. 277 * 278 * @param format_list 279 * The CLIPRDR_FORMAT_LIST structure representing the Format List PDU that 280 * was received. 281 * 282 * @return 283 * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code 284 * (non-zero) otherwise. 285 */ 286static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr, 287 CLIPRDR_CONST CLIPRDR_FORMAT_LIST* format_list) { 288 289 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 290 * callable, until after the relevant guac_rdp_clipboard structure is 291 * allocated and associated with the CliprdrClientContext */ 292 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 293 assert(clipboard != NULL); 294 295 guac_client* client = clipboard->client; 296 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 297 298 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format list."); 299 300 CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = { 301 .msgFlags = CB_RESPONSE_OK 302 }; 303 304 /* Report successful processing of format list */ 305 pthread_mutex_lock(&(rdp_client->message_lock)); 306 cliprdr->ClientFormatListResponse(cliprdr, &format_list_response); 307 pthread_mutex_unlock(&(rdp_client->message_lock)); 308 309 /* Prefer Unicode (in this case, UTF-16) */ 310 if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT)) 311 return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_UNICODETEXT); 312 313 /* Use Windows' CP-1252 if Unicode unavailable */ 314 if (guac_rdp_cliprdr_format_supported(format_list, CF_TEXT)) 315 return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT); 316 317 /* Ignore any unsupported data */ 318 guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring unsupported clipboard " 319 "data. Only Unicode and text clipboard formats are currently " 320 "supported."); 321 322 return CHANNEL_RC_OK; 323 324} 325 326/** 327 * Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data 328 * Request PDUs. The Format Data Request PDU is sent by the RDP server when 329 * requesting that clipboard data be sent, in response to a received Format 330 * List PDU. The client is required to respond with a Format Data Response PDU 331 * containing the requested data. 332 * 333 * @param cliprdr 334 * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR 335 * channel for the current RDP session. 336 * 337 * @param format_data_request 338 * The CLIPRDR_FORMAT_DATA_REQUEST structure representing the Format Data 339 * Request PDU that was received. 340 * 341 * @return 342 * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code 343 * (non-zero) otherwise. 344 */ 345static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr, 346 CLIPRDR_CONST CLIPRDR_FORMAT_DATA_REQUEST* format_data_request) { 347 348 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 349 * callable, until after the relevant guac_rdp_clipboard structure is 350 * allocated and associated with the CliprdrClientContext */ 351 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 352 assert(clipboard != NULL); 353 354 guac_client* client = clipboard->client; 355 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 356 guac_rdp_settings* settings = rdp_client->settings; 357 358 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data request."); 359 360 guac_iconv_write* remote_writer; 361 const char* input = clipboard->clipboard->buffer; 362 char* output = guac_mem_alloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); 363 364 /* Map requested clipboard format to a guac_iconv writer */ 365 switch (format_data_request->requestedFormatId) { 366 367 case CF_TEXT: 368 remote_writer = settings->clipboard_crlf ? GUAC_WRITE_CP1252_CRLF : GUAC_WRITE_CP1252; 369 break; 370 371 case CF_UNICODETEXT: 372 remote_writer = settings->clipboard_crlf ? GUAC_WRITE_UTF16_CRLF : GUAC_WRITE_UTF16; 373 break; 374 375 /* Warn if clipboard data cannot be sent as intended due to a violation 376 * of the CLIPRDR spec */ 377 default: 378 guac_client_log(client, GUAC_LOG_WARNING, "Received clipboard " 379 "data cannot be sent to the RDP server because the RDP " 380 "server has requested a clipboard format which was not " 381 "declared as available. This violates the specification " 382 "for the CLIPRDR channel."); 383 guac_mem_free(output); 384 return CHANNEL_RC_OK; 385 386 } 387 388 /* Send received clipboard data to the RDP server in the format 389 * requested */ 390 BYTE* start = (BYTE*) output; 391 guac_iconv_read* local_reader = settings->normalize_clipboard ? GUAC_READ_UTF8_NORMALIZED : GUAC_READ_UTF8; 392 guac_iconv(local_reader, &input, clipboard->clipboard->length, 393 remote_writer, &output, GUAC_COMMON_CLIPBOARD_MAX_LENGTH); 394 395 CLIPRDR_FORMAT_DATA_RESPONSE data_response = { 396 .requestedFormatData = (BYTE*) start, 397 .dataLen = ((BYTE*) output) - start, 398 .msgFlags = CB_RESPONSE_OK 399 }; 400 401 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data response."); 402 403 pthread_mutex_lock(&(rdp_client->message_lock)); 404 UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response); 405 pthread_mutex_unlock(&(rdp_client->message_lock)); 406 407 guac_mem_free(start); 408 return result; 409 410} 411 412/** 413 * Callback invoked by the FreeRDP CLIPRDR plugin for received Format Data 414 * Response PDUs. The Format Data Response PDU is sent by the RDP server when 415 * fullfilling a request for clipboard data received via a Format Data Request 416 * PDU. 417 * 418 * @param cliprdr 419 * The CliprdrClientContext structure used by FreeRDP to handle the CLIPRDR 420 * channel for the current RDP session. 421 * 422 * @param format_data_response 423 * The CLIPRDR_FORMAT_DATA_RESPONSE structure representing the Format Data 424 * Response PDU that was received. 425 * 426 * @return 427 * CHANNEL_RC_OK (zero) if the PDU was handled successfully, an error code 428 * (non-zero) otherwise. 429 */ 430static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr, 431 CLIPRDR_CONST CLIPRDR_FORMAT_DATA_RESPONSE* format_data_response) { 432 433 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 434 * callable, until after the relevant guac_rdp_clipboard structure is 435 * allocated and associated with the CliprdrClientContext */ 436 guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; 437 assert(clipboard != NULL); 438 439 guac_client* client = clipboard->client; 440 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 441 guac_rdp_settings* settings = rdp_client->settings; 442 443 guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data response."); 444 445 /* Ignore received data if copy has been disabled */ 446 if (settings->disable_copy) { 447 guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring received clipboard " 448 "data as copying from within the remote desktop has been " 449 "explicitly disabled."); 450 return CHANNEL_RC_OK; 451 } 452 453 char received_data[GUAC_COMMON_CLIPBOARD_MAX_LENGTH]; 454 455 guac_iconv_read* remote_reader; 456 const char* input = (char*) format_data_response->requestedFormatData; 457 char* output = received_data; 458 459 /* Find correct source encoding */ 460 switch (clipboard->requested_format) { 461 462 /* Non-Unicode (Windows CP-1252) */ 463 case CF_TEXT: 464 remote_reader = settings->normalize_clipboard ? GUAC_READ_CP1252_NORMALIZED : GUAC_READ_CP1252; 465 break; 466 467 /* Unicode (UTF-16) */ 468 case CF_UNICODETEXT: 469 remote_reader = settings->normalize_clipboard ? GUAC_READ_UTF16_NORMALIZED : GUAC_READ_UTF16; 470 break; 471 472 /* If the format ID stored within the guac_rdp_clipboard structure is actually 473 * not supported here, then something has been implemented incorrectly. 474 * Either incorrect values are (somehow) being stored, or support for 475 * the format indicated by that value is incomplete and must be added 476 * here. The values which may be stored within requested_format are 477 * completely within our control. */ 478 default: 479 guac_client_log(client, GUAC_LOG_DEBUG, "Requested clipboard data " 480 "in unsupported format (0x%X).", clipboard->requested_format); 481 return CHANNEL_RC_OK; 482 483 } 484 485 /* Convert, store, and forward the clipboard data received from RDP 486 * server */ 487 if (guac_iconv(remote_reader, &input, format_data_response->dataLen, 488 GUAC_WRITE_UTF8, &output, sizeof(received_data))) { 489 int length = strnlen(received_data, sizeof(received_data)); 490 guac_common_clipboard_reset(clipboard->clipboard, "text/plain"); 491 guac_common_clipboard_append(clipboard->clipboard, received_data, length); 492 guac_common_clipboard_send(clipboard->clipboard, client); 493 } 494 495 return CHANNEL_RC_OK; 496 497} 498 499/** 500 * Callback which associates handlers specific to Guacamole with the 501 * CliprdrClientContext instance allocated by FreeRDP to deal with received 502 * CLIPRDR (clipboard redirection) messages. 503 * 504 * This function is called whenever a channel connects via the PubSub event 505 * system within FreeRDP, but only has any effect if the connected channel is 506 * the CLIPRDR channel. This specific callback is registered with the PubSub 507 * system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is 508 * called. 509 * 510 * @param context 511 * The rdpContext associated with the active RDP session. 512 * 513 * @param e 514 * Event-specific arguments, mainly the name of the channel, and a 515 * reference to the associated plugin loaded for that channel by FreeRDP. 516 */ 517static void guac_rdp_cliprdr_channel_connected(rdpContext* context, 518 ChannelConnectedEventArgs* e) { 519 520 guac_client* client = ((rdp_freerdp_context*) context)->client; 521 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 522 guac_rdp_clipboard* clipboard = rdp_client->clipboard; 523 524 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 525 * callable, until after the relevant guac_rdp_clipboard structure is 526 * allocated and associated with the guac_rdp_client */ 527 assert(clipboard != NULL); 528 529 /* Ignore connection event if it's not for the CLIPRDR channel */ 530 if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0) 531 return; 532 533 /* The structure pointed to by pInterface is guaranteed to be a 534 * CliprdrClientContext if the channel is CLIPRDR */ 535 CliprdrClientContext* cliprdr = (CliprdrClientContext*) e->pInterface; 536 537 /* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with 538 * eachother */ 539 cliprdr->custom = clipboard; 540 clipboard->cliprdr = cliprdr; 541 542 cliprdr->MonitorReady = guac_rdp_cliprdr_monitor_ready; 543 cliprdr->ServerFormatList = guac_rdp_cliprdr_format_list; 544 cliprdr->ServerFormatDataRequest = guac_rdp_cliprdr_format_data_request; 545 cliprdr->ServerFormatDataResponse = guac_rdp_cliprdr_format_data_response; 546 547 guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) " 548 "channel connected."); 549 550} 551 552/** 553 * Callback which disassociates Guacamole from the CliprdrClientContext 554 * instance that was originally allocated by FreeRDP and is about to be 555 * deallocated. 556 * 557 * This function is called whenever a channel disconnects via the PubSub event 558 * system within FreeRDP, but only has any effect if the disconnected channel 559 * is the CLIPRDR channel. This specific callback is registered with the PubSub 560 * system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is 561 * called. 562 * 563 * @param context 564 * The rdpContext associated with the active RDP session. 565 * 566 * @param e 567 * Event-specific arguments, mainly the name of the channel, and a 568 * reference to the associated plugin loaded for that channel by FreeRDP. 569 */ 570static void guac_rdp_cliprdr_channel_disconnected(rdpContext* context, 571 ChannelDisconnectedEventArgs* e) { 572 573 guac_client* client = ((rdp_freerdp_context*) context)->client; 574 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 575 guac_rdp_clipboard* clipboard = rdp_client->clipboard; 576 577 /* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not 578 * callable, until after the relevant guac_rdp_clipboard structure is 579 * allocated and associated with the guac_rdp_client */ 580 assert(clipboard != NULL); 581 582 /* Ignore disconnection event if it's not for the CLIPRDR channel */ 583 if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0) 584 return; 585 586 /* Channel is no longer connected */ 587 clipboard->cliprdr = NULL; 588 589 guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) " 590 "channel disconnected."); 591 592} 593 594guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) { 595 596 /* Allocate clipboard and underlying storage */ 597 guac_rdp_clipboard* clipboard = guac_mem_zalloc(sizeof(guac_rdp_clipboard)); 598 clipboard->client = client; 599 clipboard->clipboard = guac_common_clipboard_alloc(); 600 clipboard->requested_format = CF_TEXT; 601 602 return clipboard; 603 604} 605 606void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard, 607 rdpContext* context) { 608 609 /* Attempt to load FreeRDP support for the CLIPRDR channel */ 610 if (guac_freerdp_channels_load_plugin(context, "cliprdr", NULL)) { 611 guac_client_log(clipboard->client, GUAC_LOG_WARNING, 612 "Support for the CLIPRDR channel (clipboard redirection) " 613 "could not be loaded. This support normally takes the form of " 614 "a plugin which is built into FreeRDP. Lacking this support, " 615 "clipboard will not work."); 616 return; 617 } 618 619 /* Complete RDP side of initialization when channel is connected */ 620 PubSub_SubscribeChannelConnected(context->pubSub, 621 (pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected); 622 623 /* Clean up any RDP-specific resources when channel is disconnected */ 624 PubSub_SubscribeChannelDisconnected(context->pubSub, 625 (pChannelDisconnectedEventHandler) guac_rdp_cliprdr_channel_disconnected); 626 627 guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR " 628 "(clipboard redirection) registered. Awaiting channel " 629 "connection."); 630 631} 632 633void guac_rdp_clipboard_free(guac_rdp_clipboard* clipboard) { 634 635 /* Do nothing if the clipboard is not actually allocated */ 636 if (clipboard == NULL) 637 return; 638 639 /* Free clipboard and underlying storage */ 640 guac_common_clipboard_free(clipboard->clipboard); 641 guac_mem_free(clipboard); 642 643} 644 645int guac_rdp_clipboard_handler(guac_user* user, guac_stream* stream, 646 char* mimetype) { 647 648 guac_client* client = user->client; 649 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 650 651 /* Ignore stream creation if no clipboard structure is available to handle 652 * received data */ 653 guac_rdp_clipboard* clipboard = rdp_client->clipboard; 654 if (clipboard == NULL) 655 return 0; 656 657 /* Handle any future "blob" and "end" instructions for this stream with 658 * handlers that are aware of the RDP clipboard state */ 659 stream->blob_handler = guac_rdp_clipboard_blob_handler; 660 stream->end_handler = guac_rdp_clipboard_end_handler; 661 662 /* Clear any current contents, assigning the mimetype the data which will 663 * be received */ 664 guac_common_clipboard_reset(clipboard->clipboard, mimetype); 665 return 0; 666 667} 668 669int guac_rdp_clipboard_blob_handler(guac_user* user, guac_stream* stream, 670 void* data, int length) { 671 672 guac_client* client = user->client; 673 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 674 675 /* Ignore received data if no clipboard structure is available to handle 676 * that data */ 677 guac_rdp_clipboard* clipboard = rdp_client->clipboard; 678 if (clipboard == NULL) 679 return 0; 680 681 /* Append received data to current clipboard contents */ 682 guac_common_clipboard_append(clipboard->clipboard, (char*) data, length); 683 return 0; 684 685} 686 687 688int guac_rdp_clipboard_end_handler(guac_user* user, guac_stream* stream) { 689 690 guac_client* client = user->client; 691 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 692 693 /* Ignore end of stream if no clipboard structure is available to handle 694 * the data that was received */ 695 guac_rdp_clipboard* clipboard = rdp_client->clipboard; 696 if (clipboard == NULL) 697 return 0; 698 699 /* Terminate clipboard data with NULL */ 700 guac_common_clipboard_append(clipboard->clipboard, "", 1); 701 702 /* Notify RDP server of new data, if connected */ 703 if (clipboard->cliprdr != NULL) { 704 guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data received. " 705 "Reporting availability of clipboard data to RDP server."); 706 guac_rdp_cliprdr_send_format_list(clipboard->cliprdr); 707 } 708 else 709 guac_client_log(client, GUAC_LOG_DEBUG, "Clipboard data has been " 710 "received, but cannot be sent to the RDP server because the " 711 "CLIPRDR channel is not yet connected."); 712 713 return 0; 714 715} 716