diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 2bdd8225..2849a2ca 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -60,6 +60,7 @@ libguac_client_rdp_la_SOURCES = \ rdp_settings.c \ rdp_stream.c \ resolution.c \ + svc.c \ unicode.c \ user.c @@ -76,7 +77,6 @@ noinst_HEADERS = \ guac_rdpdr/rdpdr_service.h \ guac_rdpsnd/rdpsnd_messages.h \ guac_rdpsnd/rdpsnd_service.h \ - guac_svc/svc_service.h \ audio_input.h \ client.h \ clipboard.h \ @@ -101,6 +101,7 @@ noinst_HEADERS = \ rdp_status.h \ rdp_stream.h \ resolution.h \ + svc.h \ unicode.h \ user.h @@ -125,11 +126,11 @@ libguac_client_rdp_la_LIBADD = \ # Plugins for FreeRDP # -freerdp_LTLIBRARIES = \ - libguacai-client.la -# libguacdr-client.la \ -# libguacsnd-client.la \ -# libguacsvc-client.la +freerdp_LTLIBRARIES = \ + libguacai-client.la \ + libguacsvc-client.la +# libguacdr-client.la +# libguacsnd-client.la freerdpdir = ${libdir}/freerdp2 @@ -221,25 +222,25 @@ libguacai_client_la_LIBADD = \ # Static Virtual Channels # -#libguacsvc_client_la_SOURCES = \ -# guac_svc/svc_service.c \ -# rdp_svc.c -# -#libguacsvc_client_la_CFLAGS = \ -# -Werror -Wall -Iinclude \ -# @COMMON_INCLUDE@ \ -# @COMMON_SSH_INCLUDE@ \ -# @LIBGUAC_INCLUDE@ \ -# @RDP_CFLAGS@ -# -#libguacsvc_client_la_LDFLAGS = \ -# -module -avoid-version -shared \ -# @PTHREAD_LIBS@ \ -# @RDP_LIBS@ -# -#libguacsvc_client_la_LIBADD = \ -# @COMMON_LTLIB@ \ -# @LIBGUAC_LTLIB@ +libguacsvc_client_la_SOURCES = \ + guac_svc/svc_service.c \ + svc.c + +libguacsvc_client_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @COMMON_INCLUDE@ \ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ \ + @RDP_CFLAGS@ + +libguacsvc_client_la_LDFLAGS = \ + -module -avoid-version -shared \ + @PTHREAD_LIBS@ \ + @RDP_LIBS@ + +libguacsvc_client_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ # # Optional SFTP support diff --git a/src/protocols/rdp/guac_svc/svc_service.c b/src/protocols/rdp/guac_svc/svc_service.c index 70e86f12..07f17fb9 100644 --- a/src/protocols/rdp/guac_svc/svc_service.c +++ b/src/protocols/rdp/guac_svc/svc_service.c @@ -19,13 +19,12 @@ #include "config.h" -#include "svc_service.h" +#include "svc.h" #include #include #include -#include #include #include #include @@ -33,60 +32,142 @@ #include /** - * Entry point for arbitrary SVC. + * Processes data received along an SVC via a CHANNEL_EVENT_DATA_RECEIVED + * event, forwarding the data along an established, outbound pipe stream to the + * Guacamole client. + * + * @param svc + * The guac_rdp_svc structure representing the SVC that received the data. + * + * @param data + * The data that was received. + * + * @param length + * The number of bytes received. */ -int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints) { +static void guac_rdp_svc_process_receive(guac_rdp_svc* svc, + void* data, int length) { - /* Gain access to plugin data */ - CHANNEL_ENTRY_POINTS_FREERDP* entry_points_ex = - (CHANNEL_ENTRY_POINTS_FREERDP*) pEntryPoints; + /* Fail if output not created */ + if (svc->output_pipe == NULL) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data " + "received from within the remote desktop session for SVC " + "\"%s\" are being dropped because the outbound pipe stream " + "for that SVC is not yet open. This should NOT happen.", + length, svc->channel_def.name); + return; + } - /* Allocate plugin */ - guac_svcPlugin* svc_plugin = - (guac_svcPlugin*) calloc(1, sizeof(guac_svcPlugin)); - - /* Get SVC descriptor from plugin parameters */ - guac_rdp_svc* svc = (guac_rdp_svc*) entry_points_ex->pExtendedData; - - /* Init channel def */ - guac_strlcpy(svc_plugin->plugin.channel_def.name, svc->name, - GUAC_RDP_SVC_MAX_LENGTH); - svc_plugin->plugin.channel_def.options = - CHANNEL_OPTION_INITIALIZED - | CHANNEL_OPTION_ENCRYPT_RDP - | CHANNEL_OPTION_COMPRESS_RDP; - - /* Init plugin */ - svc_plugin->svc = svc; - - /* Set callbacks */ - svc_plugin->plugin.connect_callback = guac_svc_process_connect; - svc_plugin->plugin.receive_callback = guac_svc_process_receive; - svc_plugin->plugin.event_callback = guac_svc_process_event; - svc_plugin->plugin.terminate_callback = guac_svc_process_terminate; - - /* Store plugin reference in SVC */ - svc->plugin = (rdpSvcPlugin*) svc_plugin; - - /* Finish init */ - svc_plugin_init((rdpSvcPlugin*) svc_plugin, pEntryPoints); - return 1; + /* Send blob */ + guac_protocol_send_blob(svc->client->socket, svc->output_pipe, data, length); + guac_socket_flush(svc->client->socket); } -/* - * Service Handlers +/** + * Event handler for events which deal with data transmitted over an open SVC. + * This specific implementation of the event handler currently handles only the + * CHANNEL_EVENT_DATA_RECEIVED event, delegating actual handling of that event + * to guac_rdp_svc_process_receive(). + * + * The FreeRDP requirements for this function follow those of the + * VirtualChannelOpenEventEx callback defined within Microsoft's RDP API: + * + * https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514754%28v%3dmsdn.10%29 + * + * @param user_param + * The pointer to arbitrary data originally passed via the first parameter + * of the pVirtualChannelInitEx() function call when the associated channel + * was initialized. The pVirtualChannelInitEx() function is exposed within + * the channel entry points structure. + * + * @param open_handle + * The handle which identifies the channel itself, typically referred to + * within the FreeRDP source as OpenHandle. + * + * @param event + * An integer representing the event that should be handled. This will be + * either CHANNEL_EVENT_DATA_RECEIVED, CHANNEL_EVENT_WRITE_CANCELLED, or + * CHANNEL_EVENT_WRITE_COMPLETE. + * + * @param data + * The data received, for CHANNEL_EVENT_DATA_RECEIVED events, and the value + * passed as user data to pVirtualChannelWriteEx() for + * CHANNEL_EVENT_WRITE_* events (note that user data for + * pVirtualChannelWriteEx() as implemented by FreeRDP MUST either be NULL + * or a wStream containing the data written). + * + * @param data_length + * The number of bytes of event-specific data. + * + * @param total_length + * The total number of bytes written to the RDP server in a single write + * operation. + * + * NOTE: The meaning of total_length is unclear. The above description was + * written mainly through referencing the documentation in MSDN. Real-world + * use will need to be consulted, likely within the FreeRDP source, before + * this value can be reliably used. The current implementation of this + * handler ignores this parameter. + * + * @param data_flags + * The result of a bitwise OR of the CHANNEL_FLAG_* flags which apply to + * the data received. This value is relevant only to + * CHANNEL_EVENT_DATA_RECEIVED events. Valid flags are CHANNEL_FLAG_FIRST, + * CHANNEL_FLAG_LAST, and CHANNEL_FLAG_ONLY. The flag CHANNEL_FLAG_MIDDLE + * is not itself a flag, but the absence of both CHANNEL_FLAG_FIRST and + * CHANNEL_FLAG_LAST. */ +static VOID guac_rdp_svc_handle_open_event(LPVOID user_param, + DWORD open_handle, UINT event, LPVOID data, UINT32 data_length, + UINT32 total_length, UINT32 data_flags) { -void guac_svc_process_connect(rdpSvcPlugin* plugin) { + /* Ignore all events except for received data */ + if (event != CHANNEL_EVENT_DATA_RECEIVED) + return; - /* Get corresponding guac_rdp_svc */ - guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin; - guac_rdp_svc* svc = svc_plugin->svc; + guac_rdp_svc* svc = (guac_rdp_svc*) user_param; - /* NULL out pExtendedData so we don't lose our guac_rdp_svc due to an - * automatic free() within libfreerdp */ - plugin->channel_entry_points.pExtendedData = NULL; + /* Validate relevant handle matches that of SVC */ + if (open_handle != svc->open_handle) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data " + "received from within the remote desktop session for SVC " + "\"%s\" are being dropped because the relevant open handle " + "(0x%X) does not match the open handle of the SVC (0x%X).", + data_length, svc->channel_def.name, open_handle, + svc->open_handle); + return; + } + + guac_rdp_svc_process_receive(svc, data, data_length); + +} + +/** + * Processes a CHANNEL_EVENT_CONNECTED event, completing the + * connection/initialization process of the SVC. + * + * @param svc + * The guac_rdp_svc structure representing the SVC that is now connected. + */ +static void guac_rdp_svc_process_connect(guac_rdp_svc* svc) { + + /* Open FreeRDP side of connected channel */ + UINT32 open_status = + svc->entry_points.pVirtualChannelOpenEx(svc->init_handle, + &svc->open_handle, svc->channel_def.name, + guac_rdp_svc_handle_open_event); + + /* Warn if the channel cannot be opened after all */ + if (open_status != CHANNEL_RC_OK) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "SVC \"%s\" could not " + "be opened: %s (error %i)", svc->channel_def.name, + WTSErrorToString(open_status), open_status); + return; + } + + /* SVC may now receive data from client */ + guac_rdp_svc_add(svc->client, svc); /* Create pipe */ svc->output_pipe = guac_client_alloc_stream(svc->client); @@ -96,50 +177,135 @@ void guac_svc_process_connect(rdpSvcPlugin* plugin) { /* Log connection to static channel */ guac_client_log(svc->client, GUAC_LOG_INFO, - "Static channel \"%s\" connected.", svc->name); + "Static channel \"%s\" connected.", svc->channel_def.name); } -void guac_svc_process_terminate(rdpSvcPlugin* plugin) { - - /* Get corresponding guac_rdp_svc */ - guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin; - guac_rdp_svc* svc = svc_plugin->svc; +/** + * Processes a CHANNEL_EVENT_TERMINATED event, freeing all resources associated + * with the SVC. + * + * @param svc + * The guac_rdp_svc structure representing the SVC that has been + * terminated. + */ +static void guac_rdp_svc_process_terminate(guac_rdp_svc* svc) { /* Remove and free SVC */ - guac_client_log(svc->client, GUAC_LOG_INFO, "Closing channel \"%s\"...", svc->name); - guac_rdp_remove_svc(svc->client, svc->name); + guac_client_log(svc->client, GUAC_LOG_INFO, "Closing channel \"%s\"...", svc->channel_def.name); + guac_rdp_svc_remove(svc->client, svc->channel_def.name); free(svc); - free(plugin); - } -void guac_svc_process_event(rdpSvcPlugin* plugin, wMessage* event) { - freerdp_event_free(event); -} +/** + * Event handler for events which deal with the overall lifecycle of an SVC. + * This specific implementation of the event handler currently handles only + * CHANNEL_EVENT_CONNECTED and CHANNEL_EVENT_TERMINATED events, delegating + * actual handling of those events to guac_rdp_svc_process_connect() and + * guac_rdp_svc_process_terminate() respectively. + * + * The FreeRDP requirements for this function follow those of the + * VirtualChannelInitEventEx callback defined within Microsoft's RDP API: + * + * https://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa514727%28v%3dmsdn.10%29 + * + * @param user_param + * The pointer to arbitrary data originally passed via the first parameter + * of the pVirtualChannelInitEx() function call when the associated channel + * was initialized. The pVirtualChannelInitEx() function is exposed within + * the channel entry points structure. + * + * @param init_handle + * The handle which identifies the client connection, typically referred to + * within the FreeRDP source as pInitHandle. + * + * @param event + * An integer representing the event that should be handled. This will be + * either CHANNEL_EVENT_CONNECTED, CHANNEL_EVENT_DISCONNECTED, + * CHANNEL_EVENT_INITIALIZED, CHANNEL_EVENT_TERMINATED, or + * CHANNEL_EVENT_V1_CONNECTED. + * + * @param data + * NULL in all cases except the CHANNEL_EVENT_CONNECTED event, in which + * case this is a null-terminated string containing the name of the server. + * + * @param data_length + * The number of bytes of data, if any. + */ +static VOID guac_rdp_svc_handle_init_event(LPVOID user_param, + LPVOID init_handle, UINT event, LPVOID data, UINT data_length) { -void guac_svc_process_receive(rdpSvcPlugin* plugin, - wStream* input_stream) { + guac_rdp_svc* svc = (guac_rdp_svc*) user_param; - /* Get corresponding guac_rdp_svc */ - guac_svcPlugin* svc_plugin = (guac_svcPlugin*) plugin; - guac_rdp_svc* svc = svc_plugin->svc; - - /* Fail if output not created */ - if (svc->output_pipe == NULL) { - guac_client_log(svc->client, GUAC_LOG_ERROR, - "Output for channel \"%s\" dropped.", - svc->name); + /* Validate relevant handle matches that of SVC */ + if (init_handle != svc->init_handle) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "An init event (#%i) " + "for SVC \"%s\" has been dropped because the relevant init " + "handle (0x%X) does not match the init handle of the SVC " + "(0x%X).", event, svc->channel_def.name, init_handle, + svc->init_handle); return; } - /* Send blob */ - guac_protocol_send_blob(svc->client->socket, svc->output_pipe, - Stream_Buffer(input_stream), - Stream_Length(input_stream)); + switch (event) { - guac_socket_flush(svc->client->socket); + /* The remote desktop side of the SVC has been connected */ + case CHANNEL_EVENT_CONNECTED: + guac_rdp_svc_process_connect(svc); + break; + + /* The channel has disconnected and now must be cleaned up */ + case CHANNEL_EVENT_TERMINATED: + guac_rdp_svc_process_terminate(svc); + break; + + } + +} + +/** + * Entry point for FreeRDP plugins. This function is automatically invoked when + * the plugin is loaded. + * + * @param entry_points + * Functions and data specific to the FreeRDP side of the virtual channel + * and plugin. This structure must be copied within implementation-specific + * storage such that the functions it references can be invoked when + * needed. + * + * @param init_handle + * The handle which identifies the client connection, typically referred to + * within the FreeRDP source as pInitHandle. This handle is also provided + * to the channel init event handler. The handle must eventually be used + * within the channel open event handler to obtain a handle to the channel + * itself. + * + * @return + * TRUE if the plugin has initialized successfully, FALSE otherwise. + */ +BOOL VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS entry_points, + PVOID init_handle) { + + CHANNEL_ENTRY_POINTS_FREERDP_EX* entry_points_ex = + (CHANNEL_ENTRY_POINTS_FREERDP_EX*) entry_points; + + /* Get structure representing the Guacamole side of the SVC from plugin + * parameters */ + guac_rdp_svc* svc = (guac_rdp_svc*) entry_points_ex->pExtendedData; + + /* Copy FreeRDP data into SVC structure for future reference */ + svc->entry_points = *entry_points_ex; + svc->init_handle = init_handle; + + /* Complete initialization */ + if (svc->entry_points.pVirtualChannelInitEx(svc, svc, init_handle, + &svc->channel_def, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + guac_rdp_svc_handle_init_event) != CHANNEL_RC_OK) { + return FALSE; + } + + return TRUE; } diff --git a/src/protocols/rdp/guac_svc/svc_service.h b/src/protocols/rdp/guac_svc/svc_service.h deleted file mode 100644 index 8aec6ca9..00000000 --- a/src/protocols/rdp/guac_svc/svc_service.h +++ /dev/null @@ -1,72 +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_SVC_SERVICE_H -#define __GUAC_SVC_SERVICE_H - -#include "config.h" -#include "rdp_svc.h" - -#include -#include - -/** - * Structure representing the current state of an arbitrary static virtual - * channel. - */ -typedef struct guac_svcPlugin { - - /** - * The FreeRDP parts of this plugin. This absolutely MUST be first. - * FreeRDP depends on accessing this structure as if it were an instance - * of rdpSvcPlugin. - */ - rdpSvcPlugin plugin; - - /** - * The Guacamole-specific SVC structure describing the channel this - * instance represents. - */ - guac_rdp_svc* svc; - -} guac_svcPlugin; - -/** - * Handler called when this plugin is loaded by FreeRDP. - */ -void guac_svc_process_connect(rdpSvcPlugin* plugin); - -/** - * Handler called when this plugin receives data along its designated channel. - */ -void guac_svc_process_receive(rdpSvcPlugin* plugin, - wStream* input_stream); - -/** - * Handler called when this plugin is being unloaded. - */ -void guac_svc_process_terminate(rdpSvcPlugin* plugin); - -/** - * Handler called when this plugin receives an event. - */ -void guac_svc_process_event(rdpSvcPlugin* plugin, wMessage* event); - -#endif - diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index b41527c4..e2ba5938 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -38,9 +38,7 @@ #include "rdp_glyph.h" #include "rdp_pointer.h" #include "rdp_stream.h" -#if 0 -#include "rdp_svc.h" -#endif +#include "svc.h" #ifdef ENABLE_COMMON_SSH #include "common-ssh/sftp.h" @@ -131,35 +129,15 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) { if (settings->remote_app != NULL) guac_rdp_rail_load_plugin(context); -#if 0 /* Load SVC plugin instances for all static channels */ if (settings->svc_names != NULL) { char** current = settings->svc_names; do { - - guac_rdp_svc* svc = guac_rdp_alloc_svc(client, *current); - - /* Attempt to load guacsvc plugin for new static channel */ - if (guac_freerdp_channels_load_plugin(channels, instance->settings, - "guacsvc", svc)) { - guac_client_log(client, GUAC_LOG_WARNING, - "Cannot create static channel \"%s\": failed to load guacsvc plugin.", - svc->name); - guac_rdp_free_svc(svc); - } - - /* Store and log on success */ - else { - guac_rdp_add_svc(client, svc); - guac_client_log(client, GUAC_LOG_INFO, "Created static channel \"%s\"...", - svc->name); - } - + guac_rdp_svc_load_plugin(context, *current); } while (*(++current) != NULL); } -#endif /* Load plugin providing Dynamic Virtual Channel support, if required */ if (instance->settings->SupportDynamicChannels && diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c index 3d33ea20..a497c928 100644 --- a/src/protocols/rdp/rdp_stream.c +++ b/src/protocols/rdp/rdp_stream.c @@ -22,9 +22,6 @@ #include "client.h" #include "rdp.h" #include "rdp_fs.h" -#if 0 -#include "rdp_svc.h" -#endif #include "rdp_stream.h" #include @@ -119,39 +116,6 @@ int guac_rdp_upload_file_handler(guac_user* user, guac_stream* stream, } -int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, - char* mimetype, char* name) { - -#if 0 - guac_rdp_stream* rdp_stream; - guac_rdp_svc* svc = guac_rdp_get_svc(user->client, name); - - /* Fail if no such SVC */ - if (svc == NULL) { - guac_user_log(user, GUAC_LOG_ERROR, - "Requested non-existent pipe: \"%s\".", - name); - guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)", - GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); - guac_socket_flush(user->socket); - return 0; - } - else - guac_user_log(user, GUAC_LOG_ERROR, - "Inbound half of channel \"%s\" connected.", - name); - - /* Init stream data */ - stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); - stream->blob_handler = guac_rdp_svc_blob_handler; - rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM; - rdp_stream->svc = svc; -#endif - - return 0; - -} - int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, void* data, int length) { @@ -201,23 +165,6 @@ int guac_rdp_upload_blob_handler(guac_user* user, guac_stream* stream, } -int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, - void* data, int length) { - -#if 0 - guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - - /* Write blob data to SVC directly */ - guac_rdp_svc_write(rdp_stream->svc, data, length); -#endif - - guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - -} - int guac_rdp_upload_end_handler(guac_user* user, guac_stream* stream) { guac_client* client = user->client; diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h index 6aaa1b97..3ae8b0ff 100644 --- a/src/protocols/rdp/rdp_stream.h +++ b/src/protocols/rdp/rdp_stream.h @@ -23,9 +23,6 @@ #include "config.h" #include "common/json.h" -#if 0 -#include "rdp_svc.h" -#endif #include #include @@ -113,12 +110,7 @@ typedef enum guac_rdp_stream_type { /** * An in-progress stream of a directory listing. */ - GUAC_RDP_LS_STREAM, - - /** - * The inbound half of a static virtual channel. - */ - GUAC_RDP_INBOUND_SVC_STREAM + GUAC_RDP_LS_STREAM } guac_rdp_stream_type; @@ -147,13 +139,6 @@ typedef struct guac_rdp_stream { */ guac_rdp_ls_status ls_status; -#if 0 - /** - * Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM. - */ - guac_rdp_svc* svc; -#endif - } guac_rdp_stream; /** @@ -161,21 +146,11 @@ typedef struct guac_rdp_stream { */ guac_user_file_handler guac_rdp_upload_file_handler; -/** - * Handler for inbound pipes related to static virtual channels. - */ -guac_user_pipe_handler guac_rdp_svc_pipe_handler; - /** * Handler for stream data related to file uploads. */ guac_user_blob_handler guac_rdp_upload_blob_handler; -/** - * Handler for stream data related to static virtual channels. - */ -guac_user_blob_handler guac_rdp_svc_blob_handler; - /** * Handler for end-of-stream related to file uploads. */ diff --git a/src/protocols/rdp/rdp_svc.c b/src/protocols/rdp/rdp_svc.c deleted file mode 100644 index 97434991..00000000 --- a/src/protocols/rdp/rdp_svc.c +++ /dev/null @@ -1,171 +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/list.h" -#include "rdp.h" -#include "rdp_svc.h" - -#include -#include -#include -#include - -#include - -guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name) { - - guac_rdp_svc* svc = malloc(sizeof(guac_rdp_svc)); - - /* Init SVC */ - svc->client = client; - svc->plugin = NULL; - svc->output_pipe = NULL; - - /* Init name */ - int name_length = guac_strlcpy(svc->name, name, GUAC_RDP_SVC_MAX_LENGTH); - - /* Warn about name length */ - if (name_length >= GUAC_RDP_SVC_MAX_LENGTH) - guac_client_log(client, GUAC_LOG_INFO, - "Static channel name \"%s\" exceeds maximum of %i characters " - "and will be truncated", name, GUAC_RDP_SVC_MAX_LENGTH - 1); - - return svc; -} - -void guac_rdp_free_svc(guac_rdp_svc* svc) { - free(svc); -} - -void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc) { - - /* Send pipe instruction for the SVC's output stream */ - guac_protocol_send_pipe(socket, svc->output_pipe, - "application/octet-stream", svc->name); - -} - -void guac_rdp_svc_send_pipes(guac_user* user) { - - guac_client* client = user->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - guac_common_list_lock(rdp_client->available_svc); - - /* Send pipe for each allocated SVC's output stream */ - guac_common_list_element* current = rdp_client->available_svc->head; - while (current != NULL) { - guac_rdp_svc_send_pipe(user->socket, (guac_rdp_svc*) current->data); - current = current->next; - } - - guac_common_list_unlock(rdp_client->available_svc); - -} - -void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - /* Add to list of available SVC */ - guac_common_list_lock(rdp_client->available_svc); - guac_common_list_add(rdp_client->available_svc, svc); - guac_common_list_unlock(rdp_client->available_svc); - -} - -guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_list_element* current; - guac_rdp_svc* found = NULL; - - /* For each available SVC */ - guac_common_list_lock(rdp_client->available_svc); - current = rdp_client->available_svc->head; - while (current != NULL) { - - /* If name matches, found */ - guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; - if (strcmp(current_svc->name, name) == 0) { - found = current_svc; - break; - } - - current = current->next; - - } - guac_common_list_unlock(rdp_client->available_svc); - - return found; - -} - -guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name) { - - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - guac_common_list_element* current; - guac_rdp_svc* found = NULL; - - /* For each available SVC */ - guac_common_list_lock(rdp_client->available_svc); - current = rdp_client->available_svc->head; - while (current != NULL) { - - /* If name matches, remove entry */ - guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; - if (strcmp(current_svc->name, name) == 0) { - guac_common_list_remove(rdp_client->available_svc, current); - found = current_svc; - break; - } - - current = current->next; - - } - guac_common_list_unlock(rdp_client->available_svc); - - /* Return removed entry, if any */ - return found; - -} - -void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length) { - - wStream* output_stream; - - /* Do not write of plugin not associated */ - if (svc->plugin == NULL) { - guac_client_log(svc->client, GUAC_LOG_ERROR, - "Channel \"%s\" output dropped.", - svc->name); - return; - } - - /* Build packet */ - output_stream = Stream_New(NULL, length); - Stream_Write(output_stream, data, length); - - /* Send packet */ - svc_plugin_send(svc->plugin, output_stream); - -} - diff --git a/src/protocols/rdp/rdp_svc.h b/src/protocols/rdp/rdp_svc.h deleted file mode 100644 index ebb3a13f..00000000 --- a/src/protocols/rdp/rdp_svc.h +++ /dev/null @@ -1,168 +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_SVC_H -#define __GUAC_RDP_RDP_SVC_H - -#include "config.h" - -#include -#include -#include - -/** - * The maximum number of bytes to allow within each channel name, including - * null terminator. - */ -#define GUAC_RDP_SVC_MAX_LENGTH 8 - -/** - * Structure describing a static virtual channel, and the corresponding - * Guacamole pipes. - */ -typedef struct guac_rdp_svc { - - /** - * Reference to the client owning this static channel. - */ - guac_client* client; - - /** - * Reference to associated SVC plugin. - */ - rdpSvcPlugin* plugin; - - /** - * The name of the RDP channel in use, and the name to use for each pipe. - */ - char name[GUAC_RDP_SVC_MAX_LENGTH]; - - /** - * The output pipe, opened when the RDP server receives a connection to - * the static channel. - */ - guac_stream* output_pipe; - -} guac_rdp_svc; - -/** - * Allocate a new SVC with the given name. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the virtual channel to allocate. - * - * @return - * A newly-allocated static virtual channel. - */ -guac_rdp_svc* guac_rdp_alloc_svc(guac_client* client, char* name); - -/** - * Free the given SVC. - * - * @param svc - * The static virtual channel to free. - */ -void guac_rdp_free_svc(guac_rdp_svc* svc); - -/** - * Sends the "pipe" instruction describing the given static virtual channel - * along the given socket. This pipe instruction will relate the SVC's - * underlying output stream with the SVC's name and the mimetype - * "application/octet-stream". - * - * @param socket - * The socket along which the "pipe" instruction should be sent. - * - * @param svc - * The static virtual channel that the "pipe" instruction should describe. - */ -void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc); - -/** - * Sends the "pipe" instructions describing all static virtual channels - * available to the given user along that user's socket. Each pipe instruction - * will relate the associated SVC's underlying output stream with the SVC's - * name and the mimetype "application/octet-stream". - * - * @param user - * The user to send the "pipe" instructions to. - */ -void guac_rdp_svc_send_pipes(guac_user* user); - -/** - * Add the given SVC to the list of all available SVCs. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param svc - * The static virtual channel to add to the list of all such channels - * available. - */ -void guac_rdp_add_svc(guac_client* client, guac_rdp_svc* svc); - -/** - * Retrieve the SVC with the given name from the list stored in the client. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the static virtual channel to retrieve. - * - * @return - * The static virtual channel with the given name, or NULL if no such - * virtual channel exists. - */ -guac_rdp_svc* guac_rdp_get_svc(guac_client* client, const char* name); - -/** - * Remove the SVC with the given name from the list stored in the client. - * - * @param client - * The guac_client associated with the current RDP session. - * - * @param name - * The name of the static virtual channel to remove. - * - * @return - * The static virtual channel that was removed, or NULL if no such virtual - * channel exists. - */ -guac_rdp_svc* guac_rdp_remove_svc(guac_client* client, const char* name); - -/** - * Write the given blob of data to the virtual channel. - * - * @param svc - * The static virtual channel to write data to. - * - * @param data - * The data to write. - * - * @param length - * The number of bytes to write. - */ -void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length); - -#endif - diff --git a/src/protocols/rdp/svc.c b/src/protocols/rdp/svc.c new file mode 100644 index 00000000..be420727 --- /dev/null +++ b/src/protocols/rdp/svc.c @@ -0,0 +1,227 @@ +/* + * 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 "common/list.h" +#include "rdp.h" +#include "svc.h" + +#include +#include +#include +#include +#include + +#include + +void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc) { + + /* Send pipe instruction for the SVC's output stream */ + guac_protocol_send_pipe(socket, svc->output_pipe, + "application/octet-stream", svc->channel_def.name); + +} + +void guac_rdp_svc_send_pipes(guac_user* user) { + + guac_client* client = user->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + guac_common_list_lock(rdp_client->available_svc); + + /* Send pipe for each allocated SVC's output stream */ + guac_common_list_element* current = rdp_client->available_svc->head; + while (current != NULL) { + guac_rdp_svc_send_pipe(user->socket, (guac_rdp_svc*) current->data); + current = current->next; + } + + guac_common_list_unlock(rdp_client->available_svc); + +} + +void guac_rdp_svc_add(guac_client* client, guac_rdp_svc* svc) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Add to list of available SVC */ + guac_common_list_lock(rdp_client->available_svc); + guac_common_list_add(rdp_client->available_svc, svc); + guac_common_list_unlock(rdp_client->available_svc); + +} + +guac_rdp_svc* guac_rdp_svc_get(guac_client* client, const char* name) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_list_element* current; + guac_rdp_svc* found = NULL; + + /* For each available SVC */ + guac_common_list_lock(rdp_client->available_svc); + current = rdp_client->available_svc->head; + while (current != NULL) { + + /* If name matches, found */ + guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; + if (strcmp(current_svc->channel_def.name, name) == 0) { + found = current_svc; + break; + } + + current = current->next; + + } + guac_common_list_unlock(rdp_client->available_svc); + + return found; + +} + +guac_rdp_svc* guac_rdp_svc_remove(guac_client* client, const char* name) { + + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + guac_common_list_element* current; + guac_rdp_svc* found = NULL; + + /* For each available SVC */ + guac_common_list_lock(rdp_client->available_svc); + current = rdp_client->available_svc->head; + while (current != NULL) { + + /* If name matches, remove entry */ + guac_rdp_svc* current_svc = (guac_rdp_svc*) current->data; + if (strcmp(current_svc->channel_def.name, name) == 0) { + guac_common_list_remove(rdp_client->available_svc, current); + found = current_svc; + break; + } + + current = current->next; + + } + guac_common_list_unlock(rdp_client->available_svc); + + /* Return removed entry, if any */ + return found; + +} + +void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length) { + + /* Do not write of plugin not associated */ + if (!svc->open_handle) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data " + "received from the Guacamole client for SVC \"%s\" are being " + "dropped because the remote desktop side of that SVC is " + "connected.", length, svc->channel_def.name); + return; + } + + /* FreeRDP_VirtualChannelWriteEx() assumes that sent data is dynamically + * allocated and will free() the data after it is sent */ + void* data_copy = malloc(length); + memcpy(data_copy, data, length); + + /* Send received data */ + svc->entry_points.pVirtualChannelWriteEx(svc->init_handle, + svc->open_handle, data_copy, length, + NULL /* NOTE: If non-NULL, this MUST be a pointer to a wStream + containing the supplied buffer, and that wStream will be + automatically freed when FreeRDP handles the write */); + +} + +int guac_rdp_svc_pipe_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + guac_rdp_svc* svc = guac_rdp_svc_get(user->client, name); + + /* Fail if no such SVC */ + if (svc == NULL) { + guac_user_log(user, GUAC_LOG_WARNING, "User requested non-existent " + "pipe (no such SVC configured): \"%s\"", name); + guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)", + GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST); + guac_socket_flush(user->socket); + return 0; + } + else + guac_user_log(user, GUAC_LOG_DEBUG, "Inbound half of channel \"%s\" " + "connected.", name); + + /* Init stream data */ + stream->data = svc; + stream->blob_handler = guac_rdp_svc_blob_handler; + + return 0; + +} + +int guac_rdp_svc_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + /* Write blob data to SVC directly */ + guac_rdp_svc* svc = (guac_rdp_svc*) stream->data; + guac_rdp_svc_write(svc, data, length); + + guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(user->socket); + return 0; + +} + +void guac_rdp_svc_load_plugin(rdpContext* context, char* name) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_svc* svc = calloc(1, sizeof(guac_rdp_svc)); + svc->client = client; + + /* Init FreeRDP channel definition */ + int name_length = guac_strlcpy(svc->channel_def.name, name, GUAC_RDP_SVC_MAX_LENGTH); + svc->channel_def.options = CHANNEL_OPTION_INITIALIZED + | CHANNEL_OPTION_ENCRYPT_RDP + | CHANNEL_OPTION_COMPRESS_RDP; + + /* Warn about name length */ + if (name_length >= GUAC_RDP_SVC_MAX_LENGTH) + guac_client_log(client, GUAC_LOG_WARNING, + "Static channel name \"%s\" exceeds maximum length of %i " + "characters and will be truncated to \"%s\".", + name, GUAC_RDP_SVC_MAX_LENGTH - 1, svc->channel_def.name); + + /* Attempt to load guacsvc plugin for new static channel */ + if (guac_freerdp_channels_load_plugin(context->channels, context->settings, "guacsvc", svc)) { + guac_client_log(client, GUAC_LOG_WARNING, "Cannot create static " + "channel \"%s\": failed to load guacsvc plugin.", + svc->channel_def.name); + free(svc); + } + + /* Store and log on success (SVC structure will be freed on channel termination) */ + else + guac_client_log(client, GUAC_LOG_INFO, "Created static channel " + "\"%s\"...", svc->channel_def.name); + +} + diff --git a/src/protocols/rdp/svc.h b/src/protocols/rdp/svc.h new file mode 100644 index 00000000..232b8c2c --- /dev/null +++ b/src/protocols/rdp/svc.h @@ -0,0 +1,215 @@ +/* + * 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_SVC_H +#define GUAC_RDP_SVC_H + +#include "config.h" + +#include +#include +#include +#include + +/** + * The maximum number of bytes to allow within each channel name, including + * null terminator. + */ +#define GUAC_RDP_SVC_MAX_LENGTH 8 + +/** + * Structure describing a static virtual channel, and the corresponding + * Guacamole pipes and FreeRDP resources. + */ +typedef struct guac_rdp_svc { + + /** + * Reference to the client owning this static channel. + */ + guac_client* client; + + /** + * The output pipe, opened when the RDP server receives a connection to + * the static channel. + */ + guac_stream* output_pipe; + + /** + * The definition of this static virtual channel, including its name. The + * name of the SVC is also used as the name of the associated Guacamole + * pipe streams. + */ + CHANNEL_DEF channel_def; + + /** + * Functions and data specific to the FreeRDP side of the virtual channel + * and plugin. + */ + CHANNEL_ENTRY_POINTS_FREERDP_EX entry_points; + + /** + * Handle which identifies the client connection, typically referred to + * within the FreeRDP source as pInitHandle. This handle is provided to the + * channel entry point and the channel init event handler. The handle must + * eventually be used within the channel open event handler to obtain a + * handle to the channel itself. + */ + PVOID init_handle; + + /** + * Handle which identifies the channel itself, typically referred to within + * the FreeRDP source as OpenHandle. This handle is obtained through a call + * to entry_points.pVirtualChannelOpenEx() in response to receiving a + * CHANNEL_EVENT_CONNECTED event via the init event handler. + * + * Data is received in CHANNEL_EVENT_DATA_RECEIVED events via the open + * event handler, and data is written through calls to + * entry_points.pVirtualChannelWriteEx(). + */ + DWORD open_handle; + +} guac_rdp_svc; + +/** + * Initializes arbitrary static virtual channel (SVC) support for RDP, loading + * a new instance of Guacamole's arbitrary SVC plugin for FreeRDP ("guacsvc") + * supporting the channel having the given name. Data sent from within the RDP + * session using this channel will be sent along an identically-named pipe + * stream to the Guacamole client, and data sent along a pipe stream having the + * same name will be written to the SVC and received within the RDP session. If + * failures occur while loading the plugin, messages noting the specifics of + * those failures will be logged, and support for the given channel will not be + * functional. + * + * This MUST be called within the PreConnect callback of the freerdp instance + * for static virtual channel support to be loaded. + * + * @param rdpContext + * The rdpContext associated with the FreeRDP side of the RDP connection. + * + * @param name + * The name of the SVC which should be handled by the new instance of the + * plugin. + */ +void guac_rdp_svc_load_plugin(rdpContext* context, char* name); + +/** + * Sends the "pipe" instruction describing the given static virtual channel + * along the given socket. This pipe instruction will relate the SVC's + * underlying output stream with the SVC's name and the mimetype + * "application/octet-stream". + * + * @param socket + * The socket along which the "pipe" instruction should be sent. + * + * @param svc + * The static virtual channel that the "pipe" instruction should describe. + */ +void guac_rdp_svc_send_pipe(guac_socket* socket, guac_rdp_svc* svc); + +/** + * Sends the "pipe" instructions describing all static virtual channels + * available to the given user along that user's socket. Each pipe instruction + * will relate the associated SVC's underlying output stream with the SVC's + * name and the mimetype "application/octet-stream". + * + * @param user + * The user to send the "pipe" instructions to. + */ +void guac_rdp_svc_send_pipes(guac_user* user); + +/** + * Add the given SVC to the list of all available SVCs. This function must be + * invoked after the SVC is connected for inbound pipe streams having that + * SVC's name to result in received data being sent into the RDP session. + * + * @param client + * The guac_client associated with the current RDP session. + * + * @param svc + * The static virtual channel to add to the list of all such channels + * available. + */ +void guac_rdp_svc_add(guac_client* client, guac_rdp_svc* svc); + +/** + * Retrieve the SVC with the given name from the list stored in the client. The + * requested SVC must previously have been added using guac_rdp_svc_add(). + * + * @param client + * The guac_client associated with the current RDP session. + * + * @param name + * The name of the static virtual channel to retrieve. + * + * @return + * The static virtual channel with the given name, or NULL if no such + * virtual channel exists. + */ +guac_rdp_svc* guac_rdp_svc_get(guac_client* client, const char* name); + +/** + * Removes the SVC with the given name from the list stored in the client. + * Inbound pipe streams having the given name will no longer be routed to the + * associated SVC. + * + * @param client + * The guac_client associated with the current RDP session. + * + * @param name + * The name of the static virtual channel to remove. + * + * @return + * The static virtual channel that was removed, or NULL if no such virtual + * channel exists. + */ +guac_rdp_svc* guac_rdp_svc_remove(guac_client* client, const char* name); + +/** + * Writes the given blob of data to the virtual channel such that it can be + * received within the RDP session. + * + * @param svc + * The static virtual channel to write data to. + * + * @param data + * The data to write. + * + * @param length + * The number of bytes to write. + */ +void guac_rdp_svc_write(guac_rdp_svc* svc, void* data, int length); + +/** + * Handler for "blob" instructions which automatically writes received data to + * the associated SVC using guac_rdp_svc_write(). + */ +guac_user_blob_handler guac_rdp_svc_blob_handler; + +/** + * Handler for "pipe" instructions which automatically prepares received pipe + * streams to automatically write received blobs to the SVC having the same + * name as the pipe stream. Received pipe streams are associated with the + * relevant guac_rdp_svc instance and the SVC-specific "blob" instructino + * handler (guac_rdp_svc_blob_handler). + */ +guac_user_pipe_handler guac_rdp_svc_pipe_handler; + +#endif + diff --git a/src/protocols/rdp/user.c b/src/protocols/rdp/user.c index 04238e8c..47ba93ad 100644 --- a/src/protocols/rdp/user.c +++ b/src/protocols/rdp/user.c @@ -26,9 +26,7 @@ #include "rdp.h" #include "rdp_settings.h" #include "rdp_stream.h" -#if 0 -#include "rdp_svc.h" -#endif +#include "svc.h" #ifdef ENABLE_COMMON_SSH #include "sftp.h" @@ -87,10 +85,8 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { if (rdp_client->audio) guac_audio_stream_add_user(rdp_client->audio, user); -#if 0 /* Bring user up to date with any registered static channels */ guac_rdp_svc_send_pipes(user); -#endif /* Synchronize with current display */ guac_common_display_dup(rdp_client->display, user, user->socket); @@ -112,10 +108,8 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) { /* Set generic (non-filesystem) file upload handler */ user->file_handler = guac_rdp_user_file_handler; -#if 0 /* Inbound arbitrary named pipes */ user->pipe_handler = guac_rdp_svc_pipe_handler; -#endif }