GUACAMOLE-249: Migrate SVC support to FreeRDP 2.0.0 plugin API.

This commit is contained in:
Michael Jumper 2019-12-15 18:58:04 -08:00
parent fa0ad267b8
commit 233c0555c3
11 changed files with 713 additions and 621 deletions

View File

@ -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

View File

@ -19,13 +19,12 @@
#include "config.h"
#include "svc_service.h"
#include "svc.h"
#include <stdlib.h>
#include <string.h>
#include <freerdp/constants.h>
#include <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
@ -33,60 +32,142 @@
#include <winpr/stream.h>
/**
* 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;
}

View File

@ -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 <freerdp/utils/svc_plugin.h>
#include <winpr/stream.h>
/**
* 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

View File

@ -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 &&

View File

@ -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 <freerdp/freerdp.h>
@ -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;

View File

@ -23,9 +23,6 @@
#include "config.h"
#include "common/json.h"
#if 0
#include "rdp_svc.h"
#endif
#include <guacamole/user.h>
#include <guacamole/protocol.h>
@ -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.
*/

View File

@ -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 <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/string.h>
#include <winpr/stream.h>
#include <stdlib.h>
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);
}

View File

@ -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 <freerdp/utils/svc_plugin.h>
#include <guacamole/client.h>
#include <guacamole/stream.h>
/**
* 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

227
src/protocols/rdp/svc.c Normal file
View File

@ -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 <freerdp/svc.h>
#include <guacamole/client.h>
#include <guacamole/string.h>
#include <winpr/stream.h>
#include <winpr/wtsapi.h>
#include <stdlib.h>
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);
}

215
src/protocols/rdp/svc.h Normal file
View File

@ -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 <freerdp/svc.h>
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <winpr/wtsapi.h>
/**
* 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

View File

@ -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
}