Clipboard = TRUE
AudioOutput = TRUE
RemoteApp = TRUE
+; a list of comma seperated static channels that will be proxied. This feature is useful,
+; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy.
+; Passthrough = ""
[Clipboard]
TextOnly = FALSE
static proxyPlugin demo_plugin = {
plugin_name, /* name */
plugin_desc, /* description */
+ demo_plugin_unload, /* PluginUnload */
NULL, /* ClientPreConnect */
NULL, /* ClientLoginFailure */
NULL, /* ServerPostConnect */
NULL, /* ServerChannelsFree */
demo_filter_keyboard_event, /* KeyboardEvent */
NULL, /* MouseEvent */
- demo_plugin_unload /* PluginUnload */
+ NULL, /* ClientChannelData */
+ NULL, /* ServerChannelData */
};
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager)
const char* name; /* unique module name */
const char* description; /* module description */
+ BOOL (*PluginUnload)();
+
/* proxy hooks. a module can set these function pointers to register hooks */
proxyHookFn ClientPreConnect;
proxyHookFn ClientLoginFailure;
/* proxy filters. a module can set these function pointers to register filters */
proxyFilterFn KeyboardEvent;
proxyFilterFn MouseEvent;
-
- BOOL (*PluginUnload)();
+ proxyFilterFn ClientChannelData; /* passthrough channels data */
+ proxyFilterFn ServerChannelData; /* passthrough channels data */
} proxyPlugin;
/*
UINT16 x;
UINT16 y;
} proxyMouseEventInfo;
+
+typedef struct channel_data_event_info
+{
+ /* channel metadata */
+ const char* channel_name;
+ UINT16 channel_id;
+
+ /* actual data */
+ const BYTE* data;
+ int data_len;
+} proxyChannelDataEventInfo;
#define WINPR_PACK_POP
#include <winpr/pack.h>
return FALSE;
}
+ {
+ /* open static channels for passthrough */
+ size_t i;
+
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ char* channel_name = config->Passthrough[i];
+ UINT64 channel_id;
+
+ /* only open channel if client joined with it */
+ if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
+ continue;
+
+ ps->vc_handles[i] = WTSVirtualChannelOpen(ps->vcm, WTS_CURRENT_SESSION, channel_name);
+ if (!ps->vc_handles[i])
+ {
+ LOG_ERR(TAG, ps, "WTSVirtualChannelOpen failed for passthrough channel: %s",
+ channel_name);
+
+ return FALSE;
+ }
+
+ channel_id = (UINT64)WTSChannelGetId(ps->context.peer, channel_name);
+ HashTable_Add(ps->vc_ids, channel_name, (void*)channel_id);
+ }
+ }
+
return pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_INIT, ps->pdata);
}
ps->rail = NULL;
}
+ {
+ /* close passthrough channels */
+ size_t i;
+
+ for (i = 0; i < ps->pdata->config->PassthroughCount; i++)
+ WTSVirtualChannelClose(ps->vc_handles[i]);
+ }
+
pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_FREE, ps->pdata);
}
#define TAG PROXY_TAG("client")
+static pReceiveChannelData client_receive_channel_data_original = NULL;
+
static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
{
if (!pf_context_copy_settings(ps->settings, pc->settings))
*/
LOG_INFO(TAG, pc, "Loading addins");
+ {
+ /* add passthrough channels to channel def array */
+ size_t i;
+
+ if (settings->ChannelCount + config->PassthroughCount >= settings->ChannelDefArraySize)
+ {
+ LOG_ERR(TAG, pc, "too many channels");
+ return FALSE;
+ }
+
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ const char* channel_name = config->Passthrough[i];
+ CHANNEL_DEF channel = { 0 };
+
+ /* only connect connect this channel if already joined in peer connection */
+ if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
+ {
+ LOG_INFO(TAG, ps, "client did not connected with channel %s, skipping passthrough",
+ channel_name);
+
+ continue;
+ }
+
+ channel.options = CHANNEL_OPTION_INITIALIZED; /* TODO: Export to config. */
+ strncpy(channel.name, channel_name, CHANNEL_NAME_LEN);
+
+ settings->ChannelDefArray[settings->ChannelCount++] = channel;
+ }
+ }
+
if (!pf_client_load_rdpsnd(pc, config))
{
LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
return TRUE;
}
+static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId, BYTE* data,
+ int size, int flags, int totalSize)
+{
+ pClientContext* pc = (pClientContext*)instance->context;
+ pServerContext* ps = pc->pdata->ps;
+ proxyData* pdata = ps->pdata;
+ proxyConfig* config = pdata->config;
+ size_t i;
+
+ const char* channel_name = freerdp_channels_get_name_by_id(instance, channelId);
+
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
+ {
+ proxyChannelDataEventInfo ev;
+ UINT64 server_channel_id;
+
+ ev.channel_id = channelId;
+ ev.channel_name = channel_name;
+ ev.data = (const BYTE*)data;
+ ev.data_len = size;
+
+ if (!pf_modules_run_filter(FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
+ return FALSE;
+
+ server_channel_id = (UINT64)HashTable_GetItemValue(ps->vc_ids, (void*)channel_name);
+ return ps->context.peer->SendChannelData(ps->context.peer, (UINT16)server_channel_id,
+ data, size);
+ }
+ }
+
+ return client_receive_channel_data_original(instance, channelId, data, size, flags, totalSize);
+}
+
/**
* Called after a RDP connection was successfully established.
* Settings might have changed during negotiation of client / server feature
pf_client_register_update_callbacks(update);
+ /* virtual channels receive data hook */
+ client_receive_channel_data_original = instance->ReceiveChannelData;
+ instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
+
+ /* populate channel name -> channel ids map */
+ {
+ size_t i;
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ char* channel_name = config->Passthrough[i];
+ UINT64 channel_id = (UINT64)freerdp_channels_get_id_by_name(instance, channel_name);
+ HashTable_Add(pc->vc_ids, (void*)channel_name, (void*)channel_id);
+ }
+ }
+
/*
- * after the connection fully established and settings were negotiated with target server, send
- * a reactivation sequence to the client with the negotiated settings. This way, settings are
- * synchorinized between proxy's peer and and remote target.
+ * after the connection fully established and settings were negotiated with target server,
+ * send a reactivation sequence to the client with the negotiated settings. This way,
+ * settings are synchorinized between proxy's peer and and remote target.
*/
return proxy_server_reactivate(ps, context);
}
free(pc->frames_dir);
pc->frames_dir = NULL;
+
+ HashTable_Free(pc->vc_ids);
}
static int pf_client_client_stop(rdpContext* context)
config->Clipboard = pf_config_get_bool(ini, "Channels", "Clipboard");
config->AudioOutput = pf_config_get_bool(ini, "Channels", "AudioOutput");
config->RemoteApp = pf_config_get_bool(ini, "Channels", "RemoteApp");
+ config->Passthrough = CommandLineParseCommaSeparatedValues(
+ pf_config_get_str(ini, "Channels", "Passthrough"), &config->PassthroughCount);
+
+ {
+ /* validate channel name length */
+ size_t i;
+
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ if (strlen(config->Passthrough[i]) > CHANNEL_NAME_LEN)
+ {
+ WLog_ERR(TAG, "passthrough channel: %s: name too long!", config->Passthrough[i]);
+ return FALSE;
+ }
+ }
+ }
+
return TRUE;
}
return NULL;
}
+static void pf_server_config_print_list(char** list, size_t count)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ WLog_INFO(TAG, "\t\t- %s", list[i]);
+}
+
void pf_server_config_print(proxyConfig* config)
{
WLog_INFO(TAG, "Proxy configuration:");
CONFIG_PRINT_BOOL(config, AudioOutput);
CONFIG_PRINT_BOOL(config, RemoteApp);
+ if (config->PassthroughCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy:");
+ pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
+ }
+
CONFIG_PRINT_SECTION("Clipboard");
CONFIG_PRINT_BOOL(config, TextOnly);
if (config->MaxTextLength > 0)
if (config == NULL)
return;
+ free(config->Passthrough);
free(config->CapturesDirectory);
free(config->RequiredPlugins);
free(config->Modules);
BOOL Clipboard;
BOOL AudioOutput;
BOOL RemoteApp;
+ char** Passthrough;
+ size_t PassthroughCount;
/* clipboard specific settings */
BOOL TextOnly;
#include "pf_client.h"
#include "pf_context.h"
+static wHashTable* create_channel_ids_map()
+{
+ wHashTable* table = HashTable_New(TRUE);
+ if (!table)
+ return NULL;
+
+ table->hash = HashTable_StringHash;
+ table->keyCompare = HashTable_StringCompare;
+ table->keyClone = HashTable_StringClone;
+ table->keyFree = HashTable_StringFree;
+ return table;
+}
+
/* Proxy context initialization callback */
static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* context)
{
+ proxyServer* server = (proxyServer*)client->ContextExtra;
+ proxyConfig* config = server->config;
+
context->dynvcReady = NULL;
context->vcm = WTSOpenServerA((LPSTR)client->context);
if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL)))
goto error;
+ context->vc_handles = (HANDLE*)calloc(config->PassthroughCount, sizeof(HANDLE));
+ if (!context->vc_handles)
+ goto error;
+
+ context->vc_ids = create_channel_ids_map();
+ if (!context->vc_ids)
+ goto error;
+
return TRUE;
error:
context->dynvcReady = NULL;
}
+ free(context->vc_handles);
+ context->vc_handles = NULL;
+ HashTable_Free(context->vc_ids);
+ context->vc_ids = NULL;
return FALSE;
}
CloseHandle(context->dynvcReady);
context->dynvcReady = NULL;
}
+
+ HashTable_Free(context->vc_ids);
+ free(context->vc_handles);
}
BOOL pf_context_init_server_context(freerdp_peer* client)
if (!pf_context_copy_settings(context->settings, clientSettings))
goto error;
+ pc->vc_ids = create_channel_ids_map();
+ if (!pc->vc_ids)
+ goto error;
+
return pc;
error:
freerdp_client_context_free(context);
DispServerContext* disp;
CliprdrServerContext* cliprdr;
RdpsndServerContext* rdpsnd;
+
+ HANDLE* vc_handles; /* static virtual channels open handles */
+ wHashTable* vc_ids; /* channel_name -> channel_id map */
};
typedef struct p_server_context pServerContext;
/*
* In a case when freerdp_connect fails,
* Used for NLA fallback feature, to check if the server should close the connection.
- * When it is set to TRUE, proxy's client knows it shouldn't signal the server thread to closed
- * the connection when pf_client_post_disconnect is called, because it is trying to connect
- * reconnect without NLA. It must be set to TRUE before the first try, and to FALSE after the
- * connection fully established, to ensure graceful shutdown of the connection when it will be
- * closed.
+ * When it is set to TRUE, proxy's client knows it shouldn't signal the server thread to
+ * closed the connection when pf_client_post_disconnect is called, because it is trying to
+ * connect reconnect without NLA. It must be set to TRUE before the first try, and to FALSE
+ * after the connection fully established, to ensure graceful shutdown of the connection
+ * when it will be closed.
*/
BOOL allow_next_conn_failure;
/* session capture */
char* frames_dir;
UINT64 frames_count;
+
+ wHashTable* vc_ids; /* channel_name -> channel_id map */
};
typedef struct p_client_context pClientContext;
static const char* FILTER_TYPE_STRINGS[] = {
"KEYBOARD_EVENT",
"MOUSE_EVENT",
+ "CLIENT_CHANNEL_DATA",
+ "SERVER_CHANNEL_DATA",
};
static const char* HOOK_TYPE_STRINGS[] = {
IFCALLRET(plugin->MouseEvent, result, pdata, param);
break;
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ IFCALLRET(plugin->ClientChannelData, result, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ IFCALLRET(plugin->ServerChannelData, result, pdata, param);
+ break;
default:
WLog_ERR(TAG, "invalid filter called");
}
{
FILTER_TYPE_KEYBOARD,
FILTER_TYPE_MOUSE,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA,
+ FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
FILTER_LAST
};
#define TAG PROXY_TAG("server")
+static psPeerReceiveChannelData server_receive_channel_data_original = NULL;
+
static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, char** target,
DWORD* port)
{
return TRUE;
}
+static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
+ const BYTE* data, int size, int flags,
+ int totalSize)
+{
+ pServerContext* ps = (pServerContext*)peer->context;
+ pClientContext* pc = ps->pdata->pc;
+ proxyData* pdata = pc->pdata;
+ proxyConfig* config = pdata->config;
+ size_t i;
+ const char* channel_name = WTSChannelGetName(peer, channelId);
+
+ for (i = 0; i < config->PassthroughCount; i++)
+ {
+ if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
+ {
+ proxyChannelDataEventInfo ev;
+ UINT64 client_channel_id;
+
+ ev.channel_id = channelId;
+ ev.channel_name = channel_name;
+ ev.data = data;
+ ev.data_len = size;
+
+ if (!pf_modules_run_filter(FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
+ return FALSE;
+
+ client_channel_id = (UINT64)HashTable_GetItemValue(pc->vc_ids, (void*)channel_name);
+
+ return pc->context.instance->SendChannelData(
+ pc->context.instance, (UINT16)client_channel_id, (BYTE*)data, size);
+ }
+ }
+
+ return server_receive_channel_data_original(peer, channelId, data, size, flags, totalSize);
+}
+
static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
{
pServerContext* ps = (pServerContext*)peer->context;
peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
peer->settings->MultifragMaxRequestSize = 0xFFFFFF; /* FIXME */
+ /* virtual channels receive data hook */
+ server_receive_channel_data_original = peer->ReceiveChannelData;
+ peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
+
if (ArrayList_Add(server->clients, pdata) < 0)
return FALSE;