[proxy] implement dynamic channel filter

* Allow modules to configure intercept channels
* Allow modules to rewrite packets
This commit is contained in:
akallabeth 2023-01-12 10:39:48 +01:00 committed by akallabeth
parent 2067a480e9
commit f26079edf2
7 changed files with 254 additions and 120 deletions

View File

@ -26,6 +26,7 @@
#include <freerdp/channels/wtsvc.h>
#include <freerdp/server/proxy/proxy_config.h>
#include <freerdp/server/proxy/proxy_types.h>
#define PROXY_SESSION_ID_LENGTH 32
@ -46,24 +47,6 @@ extern "C"
/* All proxy interception channels derive from this base struct
* and set their cleanup function accordingly. */
FREERDP_API void intercept_context_entry_free(void* obj);
/** @brief how is handled a channel */
typedef enum
{
PF_UTILS_CHANNEL_NOT_HANDLED, /*!< channel not handled */
PF_UTILS_CHANNEL_BLOCK, /*!< block and drop traffic on this channel */
PF_UTILS_CHANNEL_PASSTHROUGH, /*!< pass traffic from this channel */
PF_UTILS_CHANNEL_INTERCEPT, /*!< inspect traffic from this channel */
} pf_utils_channel_mode;
/** @brief result of a channel treatment */
typedef enum
{
PF_CHANNEL_RESULT_PASS, /*!< pass the packet as is */
PF_CHANNEL_RESULT_DROP, /*!< drop the packet */
PF_CHANNEL_RESULT_ERROR /*!< error during packet treatment */
} PfChannelResult;
typedef PfChannelResult (*proxyChannelDataFn)(proxyData* pdata,
const pServerStaticChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,

View File

@ -6,6 +6,8 @@
* Copyright 2019 Idan Freiberg <speidy@gmail.com>
* Copyright 2021 Armin Novak <anovak@thincast.com>
* Copyright 2021 Thincast Technologies GmbH
* Copyright 2023 Armin Novak <anovak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,114 +25,130 @@
#ifndef FREERDP_SERVER_PROXY_MODULES_API_H
#define FREERDP_SERVER_PROXY_MODULES_API_H
#include <freerdp/freerdp.h>
#include <winpr/winpr.h>
#include <winpr/stream.h>
#include <winpr/sspi.h>
#include <freerdp/server/proxy/proxy_types.h>
#define MODULE_TAG(module) "proxy.modules." module
typedef struct proxy_data proxyData;
typedef struct proxy_module proxyModule;
typedef struct proxy_plugin proxyPlugin;
typedef struct proxy_plugins_manager proxyPluginsManager;
/* hook callback. should return TRUE on success or FALSE on error. */
typedef BOOL (*proxyHookFn)(proxyPlugin*, proxyData*, void*);
/*
* Filter callback:
* It MUST return TRUE if the related event should be proxied,
* or FALSE if it should be ignored.
*/
typedef BOOL (*proxyFilterFn)(proxyPlugin*, proxyData*, void*);
/* describes a plugin: name, description and callbacks to execute.
*
* This is public API, so always add new fields at the end of the struct to keep
* some backward compatibility.
*/
struct proxy_plugin
#ifdef __cplusplus
extern "C"
{
const char* name; /* 0: unique module name */
const char* description; /* 1: module description */
#endif
UINT64 reserved1[32 - 2]; /* 2-32 */
typedef struct proxy_data proxyData;
typedef struct proxy_module proxyModule;
typedef struct proxy_plugin proxyPlugin;
typedef struct proxy_plugins_manager proxyPluginsManager;
BOOL (*PluginUnload)(proxyPlugin* plugin); /* 33 */
UINT64 reserved2[66 - 34]; /* 34 - 65 */
/* hook callback. should return TRUE on success or FALSE on error. */
typedef BOOL (*proxyHookFn)(proxyPlugin*, proxyData*, void*);
/* proxy hooks. a module can set these function pointers to register hooks */
proxyHookFn ClientInitConnect; /* 66 custom=rdpContext* */
proxyHookFn ClientUninitConnect; /* 67 custom=rdpContext* */
proxyHookFn ClientPreConnect; /* 68 custom=rdpContext* */
proxyHookFn ClientPostConnect; /* 69 custom=rdpContext* */
proxyHookFn ClientPostDisconnect; /* 70 custom=rdpContext* */
proxyHookFn ClientX509Certificate; /* 71 custom=rdpContext* */
proxyHookFn ClientLoginFailure; /* 72 custom=rdpContext* */
proxyHookFn ClientEndPaint; /* 73 custom=rdpContext* */
proxyHookFn ClientRedirect; /* 74 custom=rdpContext* */
proxyHookFn ClientLoadChannels; /* 75 custom=rdpContext* */
UINT64 reserved3[96 - 76]; /* 76-95 */
/*
* Filter callback:
* It MUST return TRUE if the related event should be proxied,
* or FALSE if it should be ignored.
*/
typedef BOOL (*proxyFilterFn)(proxyPlugin*, proxyData*, void*);
proxyHookFn ServerPostConnect; /* 96 custom=freerdp_peer* */
proxyHookFn ServerPeerActivate; /* 97 custom=freerdp_peer* */
proxyHookFn ServerChannelsInit; /* 98 custom=freerdp_peer* */
proxyHookFn ServerChannelsFree; /* 99 custom=freerdp_peer* */
proxyHookFn ServerSessionEnd; /* 100 custom=freerdp_peer* */
proxyHookFn ServerSessionInitialize; /* 101 custom=freerdp_peer* */
proxyHookFn ServerSessionStarted; /* 102 custom=freerdp_peer* */
/* describes a plugin: name, description and callbacks to execute.
*
* This is public API, so always add new fields at the end of the struct to keep
* some backward compatibility.
*/
struct proxy_plugin
{
const char* name; /* 0: unique module name */
const char* description; /* 1: module description */
UINT64 reserved4[128 - 103]; /* 103 - 127 */
UINT64 reserved1[32 - 2]; /* 2-32 */
/* proxy filters. a module can set these function pointers to register filters */
proxyFilterFn KeyboardEvent; /* 128 */
proxyFilterFn MouseEvent; /* 129 */
proxyFilterFn ClientChannelData; /* 130 passthrough channels data */
proxyFilterFn ServerChannelData; /* 131 passthrough channels data */
proxyFilterFn DynamicChannelCreate; /* 132 passthrough drdynvc channel create data */
proxyFilterFn ServerFetchTargetAddr; /* 133 */
proxyFilterFn ServerPeerLogon; /* 134 */
proxyFilterFn ChannelCreate; /* 135 passthrough drdynvc channel create data */
proxyFilterFn UnicodeEvent; /* 136 */
proxyFilterFn MouseExEvent; /* 137 */
UINT64 reserved5[160 - 138]; /* 138-159 */
BOOL (*PluginUnload)(proxyPlugin* plugin); /* 33 */
UINT64 reserved2[66 - 34]; /* 34 - 65 */
/* Runtime data fields */
proxyPluginsManager* mgr; /* 160 */ /** Set during plugin registration */
void* userdata; /* 161 */ /** Custom data provided with RegisterPlugin, memory managed
outside of plugin. */
void* custom; /* 162 */ /** Custom configuration data, must be allocated in RegisterPlugin and
freed in PluginUnload */
/* proxy hooks. a module can set these function pointers to register hooks */
proxyHookFn ClientInitConnect; /* 66 custom=rdpContext* */
proxyHookFn ClientUninitConnect; /* 67 custom=rdpContext* */
proxyHookFn ClientPreConnect; /* 68 custom=rdpContext* */
proxyHookFn ClientPostConnect; /* 69 custom=rdpContext* */
proxyHookFn ClientPostDisconnect; /* 70 custom=rdpContext* */
proxyHookFn ClientX509Certificate; /* 71 custom=rdpContext* */
proxyHookFn ClientLoginFailure; /* 72 custom=rdpContext* */
proxyHookFn ClientEndPaint; /* 73 custom=rdpContext* */
proxyHookFn ClientRedirect; /* 74 custom=rdpContext* */
proxyHookFn ClientLoadChannels; /* 75 custom=rdpContext* */
UINT64 reserved3[96 - 76]; /* 76-95 */
UINT64 reserved6[192 - 163]; /* 163-191 Add some filler data to allow for new callbacks or
* fields without breaking API */
};
proxyHookFn ServerPostConnect; /* 96 custom=freerdp_peer* */
proxyHookFn ServerPeerActivate; /* 97 custom=freerdp_peer* */
proxyHookFn ServerChannelsInit; /* 98 custom=freerdp_peer* */
proxyHookFn ServerChannelsFree; /* 99 custom=freerdp_peer* */
proxyHookFn ServerSessionEnd; /* 100 custom=freerdp_peer* */
proxyHookFn ServerSessionInitialize; /* 101 custom=freerdp_peer* */
proxyHookFn ServerSessionStarted; /* 102 custom=freerdp_peer* */
/*
* Main API for use by external modules.
* Supports:
* - Registering a plugin.
* - Setting/getting plugin's per-session specific data.
* - Aborting a session.
*/
struct proxy_plugins_manager
{
/* 0 used for registering a fresh new proxy plugin. */
BOOL (*RegisterPlugin)(struct proxy_plugins_manager* mgr, const proxyPlugin* plugin);
UINT64 reserved4[128 - 103]; /* 103 - 127 */
/* 1 used for setting plugin's per-session info. */
BOOL (*SetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*, void*);
/* proxy filters. a module can set these function pointers to register filters */
proxyFilterFn KeyboardEvent; /* 128 */
proxyFilterFn MouseEvent; /* 129 */
proxyFilterFn ClientChannelData; /* 130 passthrough channels data */
proxyFilterFn ServerChannelData; /* 131 passthrough channels data */
proxyFilterFn DynamicChannelCreate; /* 132 passthrough drdynvc channel create data */
proxyFilterFn ServerFetchTargetAddr; /* 133 */
proxyFilterFn ServerPeerLogon; /* 134 */
proxyFilterFn ChannelCreate; /* 135 passthrough drdynvc channel create data */
proxyFilterFn UnicodeEvent; /* 136 */
proxyFilterFn MouseExEvent; /* 137 */
/* 2 used for getting plugin's per-session info. */
void* (*GetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*);
/* proxy dynamic channel filters:
*
* - a function that returns the list of channels to intercept
* - a function to call with the data received
*/
proxyFilterFn DynChannelToIntercept; /* 138 */
proxyFilterFn DynChannelIntercept; /* 139 */
proxyFilterFn StaticChannelToIntercept; /* 140 */
UINT64 reserved5[160 - 141]; /* 141-159 */
/* 3 used for aborting a session. */
void (*AbortConnect)(struct proxy_plugins_manager* mgr, proxyData*);
/* Runtime data fields */
proxyPluginsManager* mgr; /* 160 */ /** Set during plugin registration */
void* userdata; /* 161 */ /** Custom data provided with RegisterPlugin, memory managed
outside of plugin. */
void* custom; /* 162 */ /** Custom configuration data, must be allocated in RegisterPlugin
and freed in PluginUnload */
UINT64 reserved[128 - 4]; /* 4-127 reserved fields */
};
UINT64 reserved6[192 - 163]; /* 163-191 Add some filler data to allow for new callbacks or
* fields without breaking API */
};
typedef BOOL (*proxyModuleEntryPoint)(proxyPluginsManager* plugins_manager, void* userdata);
/*
* Main API for use by external modules.
* Supports:
* - Registering a plugin.
* - Setting/getting plugin's per-session specific data.
* - Aborting a session.
*/
struct proxy_plugins_manager
{
/* 0 used for registering a fresh new proxy plugin. */
BOOL (*RegisterPlugin)(struct proxy_plugins_manager* mgr, const proxyPlugin* plugin);
/* 1 used for setting plugin's per-session info. */
BOOL (*SetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*, void*);
/* 2 used for getting plugin's per-session info. */
void* (*GetPluginData)(struct proxy_plugins_manager* mgr, const char*, proxyData*);
/* 3 used for aborting a session. */
void (*AbortConnect)(struct proxy_plugins_manager* mgr, proxyData*);
UINT64 reserved[128 - 4]; /* 4-127 reserved fields */
};
typedef BOOL (*proxyModuleEntryPoint)(proxyPluginsManager* plugins_manager, void* userdata);
/* filter events parameters */
#define WINPR_PACK_PUSH
@ -174,14 +192,6 @@ typedef struct
UINT32 flags;
} proxyChannelDataEventInfo;
typedef enum
{
PROXY_FETCH_TARGET_METHOD_DEFAULT,
PROXY_FETCH_TARGET_METHOD_CONFIG,
PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO,
PROXY_FETCH_TARGET_USE_CUSTOM_ADDR
} ProxyFetchTargetMethod;
typedef struct
{
/* out values */
@ -200,7 +210,32 @@ typedef struct server_peer_logon
const SEC_WINNT_AUTH_IDENTITY* identity;
BOOL automatic;
} proxyServerPeerLogon;
typedef struct dyn_channel_intercept_data
{
const char* name;
UINT32 channelId;
wStream* data;
BOOL isBackData;
BOOL first;
BOOL last;
BOOL rewritten;
size_t packetSize;
PfChannelResult result;
} proxyDynChannelInterceptData;
typedef struct dyn_channel_to_intercept_data
{
const char* name;
UINT32 channelId;
BOOL intercept;
} proxyChannelToInterceptData;
#define WINPR_PACK_POP
#include <winpr/pack.h>
#ifdef __cplusplus
}
#endif
#endif /* FREERDP_SERVER_PROXY_MODULES_API_H */

View File

@ -0,0 +1,48 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy enum types
*
* Copyright 2023 Armin Novak <armin.novak@thincast.com>
* Copyright 2023 Thincast Technologies GmbH
*
* Licensed 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 FREERDP_SERVER_PROXY_TYPES_H
#define FREERDP_SERVER_PROXY_TYPES_H
/** @brief how is handled a channel */
typedef enum
{
PF_UTILS_CHANNEL_NOT_HANDLED, /*!< channel not handled */
PF_UTILS_CHANNEL_BLOCK, /*!< block and drop traffic on this channel */
PF_UTILS_CHANNEL_PASSTHROUGH, /*!< pass traffic from this channel */
PF_UTILS_CHANNEL_INTERCEPT, /*!< inspect traffic from this channel */
} pf_utils_channel_mode;
/** @brief result of a channel treatment */
typedef enum
{
PF_CHANNEL_RESULT_PASS, /*!< pass the packet as is */
PF_CHANNEL_RESULT_DROP, /*!< drop the packet */
PF_CHANNEL_RESULT_ERROR /*!< error during packet treatment */
} PfChannelResult;
typedef enum
{
PROXY_FETCH_TARGET_METHOD_DEFAULT,
PROXY_FETCH_TARGET_METHOD_CONFIG,
PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO,
PROXY_FETCH_TARGET_USE_CUSTOM_ADDR
} ProxyFetchTargetMethod;
#endif /* FREERDP_SERVER_PROXY_TYPES_H */

View File

@ -87,6 +87,35 @@ typedef enum
DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
} DynvcReadResult;
static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel,
BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket,
BOOL lastPacket)
{
WINPR_ASSERT(ps);
WINPR_ASSERT(channel);
WINPR_ASSERT(tracker);
WINPR_ASSERT(ps->pdata);
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
proxyDynChannelInterceptData dyn = { .name = channel->channelName,
.channelId = channel->channelId,
.data = currentPacket,
.isBackData = isBackData,
.first = firstPacket,
.last = lastPacket,
.rewritten = FALSE,
.packetSize = channelTracker_getCurrentPacketSize(tracker),
.result = PF_CHANNEL_RESULT_ERROR };
Stream_SealLength(dyn.data);
if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn))
return PF_CHANNEL_RESULT_ERROR;
channelTracker_setCurrentPacketSize(tracker, dyn.packetSize);
if (dyn.rewritten)
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
return dyn.result;
}
static pServerDynamicChannelContext* DynamicChannelContext_new(pServerContext* ps, const char* name,
UINT32 id)
{
@ -106,15 +135,24 @@ static pServerDynamicChannelContext* DynamicChannelContext_new(pServerContext* p
return NULL;
}
ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
ret->frontTracker.dataCallback = data_cb;
ret->backTracker.dataCallback = data_cb;
proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE };
if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) &&
dyn.intercept)
ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
else
ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
ret->openStatus = CHANNEL_OPENSTATE_OPENED;
ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT);
return ret;
}
static void DynamicChannelContext_free(pServerDynamicChannelContext* c)
static void DynamicChannelContext_free(void* ptr)
{
pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr;
if (!c)
return;
@ -137,8 +175,10 @@ static UINT32 ChannelId_Hash(const void* key)
return *v;
}
static BOOL ChannelId_Compare(const UINT32* v1, const UINT32* v2)
static BOOL ChannelId_Compare(const void* objA, const void* objB)
{
const UINT32* v1 = objA;
const UINT32* v2 = objB;
return (*v1 == *v2);
}
@ -557,10 +597,10 @@ static DynChannelContext* DynChannelContext_new(proxyData* pdata,
goto fail;
obj = HashTable_KeyObject(dyn->channels);
obj->fnObjectEquals = (OBJECT_EQUALS_FN)ChannelId_Compare;
obj->fnObjectEquals = ChannelId_Compare;
obj = HashTable_ValueObject(dyn->channels);
obj->fnObjectFree = (OBJECT_FREE_FN)DynamicChannelContext_free;
obj->fnObjectFree = DynamicChannelContext_free;
return dyn;
@ -575,9 +615,11 @@ static PfChannelResult pf_dynvc_back_data(proxyData* pdata,
size_t totalSize)
{
WINPR_ASSERT(channel);
DynChannelContext* dyn = (DynChannelContext*)channel->context;
WINPR_UNUSED(pdata);
WINPR_ASSERT(dyn);
return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize);
}
@ -587,9 +629,11 @@ static PfChannelResult pf_dynvc_front_data(proxyData* pdata,
size_t totalSize)
{
WINPR_ASSERT(channel);
DynChannelContext* dyn = (DynChannelContext*)channel->context;
WINPR_UNUSED(pdata);
WINPR_ASSERT(dyn);
return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize);
}

View File

@ -29,6 +29,8 @@
#include "pf_client.h"
#include "pf_utils.h"
#include "proxy_modules.h"
#include <freerdp/server/proxy/proxy_context.h>
#include "channels/pf_channel_rdpdr.h"
@ -65,7 +67,14 @@ pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const
return NULL;
}
ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
proxyChannelToInterceptData channel = { .name = name, .channelId = id, .intercept = FALSE };
if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_STATIC_INTERCEPT_LIST, ps->pdata,
&channel) &&
channel.intercept)
ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
else
ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
return ret;
}

View File

@ -292,6 +292,18 @@ static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap
result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param);
break;
case FILTER_TYPE_INTERCEPT_CHANNEL:
result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param);
break;
case FILTER_TYPE_DYN_INTERCEPT_LIST:
result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param);
break;
case FILTER_TYPE_STATIC_INTERCEPT_LIST:
result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param);
break;
case FILTER_LAST:
default:
WLog_ERR(TAG, "invalid filter called");

View File

@ -39,6 +39,9 @@ typedef enum
FILTER_TYPE_SERVER_PEER_LOGON, /* proxyServerPeerLogon */
FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
FILTER_TYPE_STATIC_INTERCEPT_LIST, /* proxyChannelToInterceptData */
FILTER_TYPE_DYN_INTERCEPT_LIST, /* proxyChannelToInterceptData */
FILTER_TYPE_INTERCEPT_CHANNEL, /* proxyDynChannelInterceptData */
FILTER_LAST
} PF_FILTER_TYPE;