server: proxy: support static vc passthrough
authorKobi Mizrachi <kmizrachi18@gmail.com>
Sun, 2 Feb 2020 13:15:28 +0000 (15:15 +0200)
committerakallabeth <akallabeth@users.noreply.github.com>
Wed, 26 Feb 2020 10:47:01 +0000 (11:47 +0100)
12 files changed:
server/proxy/config.ini
server/proxy/modules/demo/demo.cpp
server/proxy/modules/modules_api.h
server/proxy/pf_channels.c
server/proxy/pf_client.c
server/proxy/pf_config.c
server/proxy/pf_config.h
server/proxy/pf_context.c
server/proxy/pf_context.h
server/proxy/pf_modules.c
server/proxy/pf_modules.h
server/proxy/pf_server.c

index 3b31b56..16e6f1d 100644 (file)
@@ -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
index f105e29..f50789a 100644 (file)
@@ -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)
index 7330169..a99ebc4 100644 (file)
@@ -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 <winpr/pack.h>
 
index 08fd965..1c6620f 100644 (file)
@@ -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);
 }
index 5c7ee62..bcf4aaf 100644 (file)
@@ -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)
index d14c682..ff78b70 100644 (file)
@@ -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);
index 199c585..033f680 100644 (file)
@@ -58,6 +58,8 @@ struct proxy_config
        BOOL Clipboard;
        BOOL AudioOutput;
        BOOL RemoteApp;
+       char** Passthrough;
+       size_t PassthroughCount;
 
        /* clipboard specific settings */
        BOOL TextOnly;
index c143129..552eb79 100644 (file)
 #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);
index 597c84c..7c49fa7 100644 (file)
@@ -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;
 
index 1584d1d..397cb3f 100644 (file)
@@ -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");
                }
index efcf066..a8efd7f 100644 (file)
@@ -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
 };
index d8193a3..d0fec5e 100644 (file)
@@ -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;