cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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