From 079871ac655a7ebf7060d58ed6a68c2e24202231 Mon Sep 17 00:00:00 2001 From: Kobi Mizrachi Date: Sun, 2 Feb 2020 15:15:28 +0200 Subject: [PATCH] server: proxy: support static vc passthrough --- server/proxy/config.ini | 3 ++ server/proxy/modules/demo/demo.cpp | 4 +- server/proxy/modules/modules_api.h | 17 ++++++- server/proxy/pf_channels.c | 35 +++++++++++++++ server/proxy/pf_client.c | 91 ++++++++++++++++++++++++++++++++++++-- server/proxy/pf_config.c | 32 ++++++++++++++ server/proxy/pf_config.h | 2 + server/proxy/pf_context.c | 35 +++++++++++++++ server/proxy/pf_context.h | 15 ++++--- server/proxy/pf_modules.c | 9 ++++ server/proxy/pf_modules.h | 2 + server/proxy/pf_server.c | 42 ++++++++++++++++++ 12 files changed, 276 insertions(+), 11 deletions(-) diff --git a/server/proxy/config.ini b/server/proxy/config.ini index 3b31b56..16e6f1d 100644 --- a/server/proxy/config.ini +++ b/server/proxy/config.ini @@ -35,6 +35,9 @@ DisplayControl = TRUE 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 diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp index f105e29..f50789a 100644 --- a/server/proxy/modules/demo/demo.cpp +++ b/server/proxy/modules/demo/demo.cpp @@ -53,6 +53,7 @@ static BOOL demo_plugin_unload() static proxyPlugin demo_plugin = { plugin_name, /* name */ plugin_desc, /* description */ + demo_plugin_unload, /* PluginUnload */ NULL, /* ClientPreConnect */ NULL, /* ClientLoginFailure */ NULL, /* ServerPostConnect */ @@ -60,7 +61,8 @@ static proxyPlugin demo_plugin = { 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) diff --git a/server/proxy/modules/modules_api.h b/server/proxy/modules/modules_api.h index 7330169..a99ebc4 100644 --- a/server/proxy/modules/modules_api.h +++ b/server/proxy/modules/modules_api.h @@ -44,6 +44,8 @@ typedef struct proxy_plugin 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; @@ -54,8 +56,8 @@ typedef struct proxy_plugin /* 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; /* @@ -95,6 +97,17 @@ typedef struct proxy_mouse_event_info 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 diff --git a/server/proxy/pf_channels.c b/server/proxy/pf_channels.c index 08fd965..1c6620f 100644 --- a/server/proxy/pf_channels.c +++ b/server/proxy/pf_channels.c @@ -230,6 +230,33 @@ BOOL pf_server_channels_init(pServerContext* ps) 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); } @@ -265,5 +292,13 @@ void pf_server_channels_free(pServerContext* ps) 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); } diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c index 5c7ee62..bcf4aaf 100644 --- a/server/proxy/pf_client.c +++ b/server/proxy/pf_client.c @@ -39,6 +39,8 @@ #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)) @@ -149,6 +151,37 @@ static BOOL pf_client_pre_connect(freerdp* instance) */ 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"); @@ -164,6 +197,41 @@ static BOOL pf_client_pre_connect(freerdp* instance) 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 @@ -224,10 +292,25 @@ static BOOL pf_client_post_connect(freerdp* instance) 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); } @@ -506,6 +589,8 @@ static void pf_client_client_free(freerdp* instance, rdpContext* context) free(pc->frames_dir); pc->frames_dir = NULL; + + HashTable_Free(pc->vc_ids); } static int pf_client_client_stop(rdpContext* context) diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c index d14c682..ff78b70 100644 --- a/server/proxy/pf_config.c +++ b/server/proxy/pf_config.c @@ -154,6 +154,23 @@ static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config) 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; } @@ -284,6 +301,14 @@ out: 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:"); @@ -321,6 +346,12 @@ void pf_server_config_print(proxyConfig* config) 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) @@ -336,6 +367,7 @@ void pf_server_config_free(proxyConfig* config) if (config == NULL) return; + free(config->Passthrough); free(config->CapturesDirectory); free(config->RequiredPlugins); free(config->Modules); diff --git a/server/proxy/pf_config.h b/server/proxy/pf_config.h index 199c585..033f680 100644 --- a/server/proxy/pf_config.h +++ b/server/proxy/pf_config.h @@ -58,6 +58,8 @@ struct proxy_config BOOL Clipboard; BOOL AudioOutput; BOOL RemoteApp; + char** Passthrough; + size_t PassthroughCount; /* clipboard specific settings */ BOOL TextOnly; diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c index c143129..552eb79 100644 --- a/server/proxy/pf_context.c +++ b/server/proxy/pf_context.c @@ -25,9 +25,25 @@ #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); @@ -38,6 +54,14 @@ static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* co 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: @@ -50,6 +74,10 @@ error: context->dynvcReady = NULL; } + free(context->vc_handles); + context->vc_handles = NULL; + HashTable_Free(context->vc_ids); + context->vc_ids = NULL; return FALSE; } @@ -70,6 +98,9 @@ static void client_to_proxy_context_free(freerdp_peer* client, pServerContext* c CloseHandle(context->dynvcReady); context->dynvcReady = NULL; } + + HashTable_Free(context->vc_ids); + free(context->vc_handles); } BOOL pf_context_init_server_context(freerdp_peer* client) @@ -157,6 +188,10 @@ pClientContext* pf_context_create_client_context(rdpSettings* clientSettings) 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); diff --git a/server/proxy/pf_context.h b/server/proxy/pf_context.h index 597c84c..7c49fa7 100644 --- a/server/proxy/pf_context.h +++ b/server/proxy/pf_context.h @@ -56,6 +56,9 @@ struct p_server_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; @@ -78,17 +81,19 @@ struct p_client_context /* * 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; diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c index 1584d1d..397cb3f 100644 --- a/server/proxy/pf_modules.c +++ b/server/proxy/pf_modules.c @@ -42,6 +42,8 @@ typedef BOOL (*moduleEntryPoint)(proxyPluginsManager* plugins_manager); static const char* FILTER_TYPE_STRINGS[] = { "KEYBOARD_EVENT", "MOUSE_EVENT", + "CLIENT_CHANNEL_DATA", + "SERVER_CHANNEL_DATA", }; static const char* HOOK_TYPE_STRINGS[] = { @@ -144,6 +146,13 @@ BOOL pf_modules_run_filter(PF_FILTER_TYPE type, proxyData* pdata, void* param) 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"); } diff --git a/server/proxy/pf_modules.h b/server/proxy/pf_modules.h index efcf066..a8efd7f 100644 --- a/server/proxy/pf_modules.h +++ b/server/proxy/pf_modules.h @@ -31,6 +31,8 @@ enum _PF_FILTER_TYPE { FILTER_TYPE_KEYBOARD, FILTER_TYPE_MOUSE, + FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, + FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, FILTER_LAST }; diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c index d8193a3..d0fec5e 100644 --- a/server/proxy/pf_server.c +++ b/server/proxy/pf_server.c @@ -46,6 +46,8 @@ #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) { @@ -196,6 +198,42 @@ static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer) 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; @@ -257,6 +295,10 @@ static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer) 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; -- 2.7.4