server/proxy: Add external filters support

This commit is contained in:
kubistika 2019-05-12 20:48:51 +03:00
parent 3d346b69df
commit a39658fc2a
12 changed files with 530 additions and 42 deletions

View File

@ -43,6 +43,8 @@ set(${MODULE_PREFIX}_SRCS
pf_config.h
pf_graphics.c
pf_graphics.h
pf_filters.c
pf_filters.h
pf_log.h)
# On windows create dll version information.

View File

@ -31,3 +31,7 @@ RdpSecurity = 1
WhitelistMode = 0
AllowedChannels = "cliprdr,Microsoft::Windows::RDS::Video::Control"
DeniedChannels = "Microsoft::Windows::RDS::Geometry"
[Filters]
; FilterName = FilterPath
DemoFilter = "server/proxy/demo.so"

View File

@ -0,0 +1,25 @@
#include "filters_api.h"
static PF_FILTER_RESULT demo_filter_keyboard_event(connectionInfo* info, void* param)
{
proxyKeyboardEventInfo* event_data = (proxyKeyboardEventInfo*) param;
return FILTER_PASS;
}
static PF_FILTER_RESULT demo_filter_mouse_event(connectionInfo* info, void* param)
{
proxyMouseEventInfo* event_data = (proxyMouseEventInfo*) param;
if (event_data->x % 100 == 0)
{
return FILTER_DROP;
}
return FILTER_PASS;
}
bool filter_init(proxyEvents* events)
{
events->KeyboardEvent = demo_filter_keyboard_event;
events->MouseEvent = demo_filter_mouse_event;
}

View File

@ -0,0 +1,69 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server
*
* Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
* Copyright 2019 Idan Freiberg <speidy@gmail.com>
*
* 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_FILTERS_API_H
#define FREERDP_SERVER_PROXY_FILTERS_API_H
#include <inttypes.h>
#include <stdbool.h>
enum pf_filter_result {
FILTER_PASS = 0,
FILTER_DROP,
FILTER_IGNORE
};
typedef enum pf_filter_result PF_FILTER_RESULT;
typedef struct connection_info connectionInfo;
typedef struct proxy_events proxyEvents;
typedef struct proxy_keyboard_event_info proxyKeyboardEventInfo;
typedef struct proxy_mouse_event_info proxyMouseEventInfo;
typedef PF_FILTER_RESULT(*proxyEvent)(connectionInfo* info, void* param);
struct connection_info {
char* TargetHostname;
char* ClientHostname;
char* Username;
};
struct proxy_events {
proxyEvent KeyboardEvent;
proxyEvent MouseEvent;
};
#pragma pack(push, 1)
struct proxy_keyboard_event_info {
uint16_t flags;
uint16_t rdp_scan_code;
};
struct proxy_mouse_event_info {
uint16_t flags;
uint16_t x;
uint16_t y;
};
#pragma pack(pop)
/* implement this method */
bool filter_init(proxyEvents* events);
#endif /* FREERDP_SERVER_PROXY_FILTERS_API_H */

View File

@ -22,6 +22,8 @@
#include <stdio.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/collections.h>
#include "pf_log.h"
#include "pf_server.h"
#include "pf_config.h"
@ -30,39 +32,49 @@
#define CHANNELS_SEPERATOR ","
static char** parse_channels_from_str(const char* str, UINT32* length)
wArrayList* parse_string_array_from_str(const char* str)
{
char* s = strdup(str);
size_t tokens_alloc = 1;
size_t tokens_count = 0;
char** tokens = calloc(tokens_alloc, sizeof(char*));
wArrayList* list = ArrayList_New(FALSE);
if (list == NULL)
{
WLog_ERR(TAG, "parse_string_array_from_str(): ArrayList_New failed!");
return NULL;
}
char* s = _strdup(str);
char* temp = s;
char* token;
while ((token = StrSep(&s, CHANNELS_SEPERATOR)) != NULL)
if (s == NULL)
{
if (tokens_count == tokens_alloc)
WLog_ERR(TAG, "parse_string_array_from_str(): strdup failed!");
goto error;
}
while ((token = StrSep(&temp, CHANNELS_SEPERATOR)) != NULL)
{
char* current_token = _strdup(token);
if (current_token == NULL)
{
tokens_alloc *= 2;
tokens = realloc(tokens, tokens_alloc * sizeof(char*));
WLog_ERR(TAG, "parse_string_array_from_str(): strdup failed!");
goto error;
}
tokens[tokens_count++] = strdup(token);
if (ArrayList_Add(list, current_token) < 0)
{
free(current_token);
goto error;
}
}
if ((tokens_count == 0) || (tokens_count > UINT32_MAX))
{
free(tokens);
tokens = NULL;
tokens_count = 0;
}
else
{
tokens = realloc(tokens, tokens_count * sizeof(char*));
}
*length = (DWORD)tokens_count;
free(s);
return tokens;
return list;
error:
free(s);
ArrayList_Free(list);
return NULL;
}
static BOOL pf_server_is_config_valid(proxyConfig* config)
@ -100,7 +112,9 @@ static BOOL pf_server_is_config_valid(proxyConfig* config)
DWORD pf_server_load_config(const char* path, proxyConfig* config)
{
const char* input;
char** filters_names;
int rc;
int filters_count = 0;
DWORD result = CONFIG_PARSE_ERROR;
wIniFile* ini = IniFile_New();
@ -141,10 +155,11 @@ DWORD pf_server_load_config(const char* path, proxyConfig* config)
/* channels filtering */
config->WhitelistMode = IniFile_GetKeyValueInt(ini, "Channels", "WhitelistMode");
input = IniFile_GetKeyValueString(ini, "Channels", "AllowedChannels");
/* filters api */
if (input)
{
config->AllowedChannels = parse_channels_from_str(input, &config->AllowedChannelsCount);
config->AllowedChannels = parse_string_array_from_str(input);
if (config->AllowedChannels == NULL)
goto out;
@ -154,13 +169,34 @@ DWORD pf_server_load_config(const char* path, proxyConfig* config)
if (input)
{
config->BlockedChannels = parse_channels_from_str(input, &config->BlockedChannelsCount);
config->BlockedChannels = parse_string_array_from_str(input);
if (config->BlockedChannels == NULL)
goto out;
}
result = CONFIG_PARSE_SUCCESS;
if (!pf_filters_init(&config->Filters))
goto out;
filters_names = IniFile_GetSectionKeyNames(ini, "Filters", &filters_count);
for (int i = 0; i < filters_count; i++)
{
char* filter_name = filters_names[i];
const char* path = IniFile_GetKeyValueString(ini, "Filters", filter_name);
if (!pf_filters_register_new(config->Filters, path, filter_name))
{
WLog_DBG(TAG, "pf_server_load_config(): failed to register %s (%s)", filter_name, path);
}
else
{
WLog_DBG(TAG, "pf_server_load_config(): registered filter %s (%s) successfully", filter_name, path);
}
}
out:
IniFile_Free(ini);
@ -172,16 +208,9 @@ out:
void pf_server_config_free(proxyConfig* config)
{
UINT32 i;
for (i = 0; i < config->AllowedChannelsCount; i++)
free(config->AllowedChannels[i]);
for (i = 0; i < config->BlockedChannelsCount; i++)
free(config->BlockedChannels[i]);
free(config->AllowedChannels);
free(config->BlockedChannels);
pf_filters_unregister_all(config->Filters);
ArrayList_Free(config->AllowedChannels);
ArrayList_Free(config->BlockedChannels);
free(config->TargetHost);
free(config->Host);
free(config);

View File

@ -28,6 +28,8 @@
#include <winpr/ini.h>
#include "pf_filters.h"
struct proxy_config
{
/* server */
@ -59,8 +61,8 @@ struct proxy_config
char** AllowedChannels;
UINT32 AllowedChannelsCount;
char** BlockedChannels;
UINT32 BlockedChannelsCount;
/* filters */
filters_list* Filters;
};
typedef struct proxy_config proxyConfig;

View File

@ -79,3 +79,37 @@ rdpContext* p_client_context_create(rdpSettings* clientSettings,
settings->RedirectClipboard = FALSE;
return context;
}
static void pf_context_connection_info_free(connectionInfo* info)
{
free(info->TargetHostname);
free(info->ClientHostname);
free(info->Username);
free(info);
}
proxyData* pf_context_proxy_data_new()
{
proxyData* pdata = malloc(sizeof(proxyData));
if (pdata == NULL)
{
return NULL;
}
pdata->info = pf_context_connection_info_new();
if (pdata->info == NULL)
{
free(pdata);
return NULL;
}
return pdata;
}
void pf_context_proxy_data_free(proxyData* pdata)
{
pf_context_connection_info_free(pdata->info);
free(pdata);
}

View File

@ -29,6 +29,7 @@
#include <freerdp/server/rdpgfx.h>
#include "pf_config.h"
#include "pf_server.h"
#include "pf_filters.h"
typedef struct proxy_data proxyData;
@ -75,9 +76,16 @@ struct proxy_data
pClientContext* pc;
HANDLE connectionClosed;
connectionInfo* info;
filters_list* filters;
};
BOOL init_p_server_context(freerdp_peer* client);
rdpContext* p_client_context_create(rdpSettings* clientSettings, char* host, DWORD port);
proxyData* pf_context_proxy_data_new();
void pf_context_proxy_data_free(proxyData* pdata);
connectionInfo* pf_context_connection_info_new();
void pf_context_connection_info_free(connectionInfo* info);
#endif /* FREERDP_SERVER_PROXY_PFCONTEXT_H */

218
server/proxy/pf_filters.c Normal file
View File

@ -0,0 +1,218 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server
*
* Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
* Copyright 2019 Idan Freiberg <speidy@gmail.com>
*
* 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.
*/
#include <assert.h>
#include <winpr/wlog.h>
#include <winpr/library.h>
#include <freerdp/api.h>
#include "pf_log.h"
#include "pf_filters.h"
#define TAG PROXY_TAG("filters")
#define FILTER_INIT_METHOD "filter_init"
static const char* FILTER_RESULT_STRINGS[] =
{
"FILTER_PASS",
"FILTER_DROP",
"FILTER_IGNORE",
};
static const char* EVENT_TYPE_STRINGS[] =
{
"KEYBOARD_EVENT",
"MOUSE_EVENT",
};
static const char* pf_filters_get_filter_result_string(PF_FILTER_RESULT result)
{
if (result >= FILTER_PASS && result <= FILTER_IGNORE)
return FILTER_RESULT_STRINGS[result];
else
return "FILTER_UNKNOWN";
}
static const char* pf_filters_get_event_type_string(PF_FILTER_TYPE result)
{
if (result >= FILTER_TYPE_KEYBOARD && result <= FILTER_TYPE_MOUSE)
return EVENT_TYPE_STRINGS[result];
else
return "EVENT_UNKNOWN";
}
BOOL pf_filters_init(filters_list** list)
{
if (list == NULL)
{
WLog_ERR(TAG, "pf_filters_init(): list == NULL");
return FALSE;
}
*list = ArrayList_New(FALSE);
if (*list == NULL)
{
WLog_ERR(TAG, "pf_filters_init(): ArrayList_New failed!");
return FALSE;
}
return TRUE;
}
PF_FILTER_RESULT pf_filters_run_by_type(filters_list* list, PF_FILTER_TYPE type,
connectionInfo* info,
void* param)
{
proxyFilter* filter;
proxyEvents* events;
PF_FILTER_RESULT result = FILTER_PASS;
const size_t count = (size_t) ArrayList_Count(list);
size_t index;
for (index = 0; index < count; index++)
{
filter = (proxyFilter*) ArrayList_GetItem(list, index);
events = filter->events;
WLog_DBG(TAG, "pf_filters_run_by_type(): Running filter: %s", filter->name);
switch (type)
{
case FILTER_TYPE_KEYBOARD:
IFCALLRET(events->KeyboardEvent, result, info, param);
break;
case FILTER_TYPE_MOUSE:
IFCALLRET(events->MouseEvent, result, info, param);
break;
}
if (result != FILTER_PASS)
{
/* Filter returned FILTER_DROP or FILTER_IGNORE. There's no need to call next filters. */
WLog_INFO(TAG, "Filter %s [%s] returned %s", filter->name,
pf_filters_get_event_type_string(type), pf_filters_get_filter_result_string(result));
return result;
}
}
/* all filters returned FILTER_PASS */
return FILTER_PASS;
}
static void pf_filters_filter_free(proxyFilter* filter)
{
assert(filter != NULL);
FreeLibrary(filter->handle);
free(filter->name);
free(filter->events);
free(filter);
}
void pf_filters_unregister_all(filters_list* list)
{
if (list == NULL)
return;
const size_t count = (size_t) ArrayList_Count(list);
size_t index;
for (index = 0; index < count; index++)
{
proxyFilter* filter = (proxyFilter*) ArrayList_GetItem(list, index);
WLog_DBG(TAG, "pf_filters_unregister_all(): freeing filter: %s", filter->name);
pf_filters_filter_free(filter);
}
ArrayList_Free(list);
}
BOOL pf_filters_register_new(filters_list* list, const char* module_path, const char* filter_name)
{
proxyEvents* events = NULL;
proxyFilter* filter = NULL;
HMODULE handle = NULL;
filterInitFn fn;
if (list == NULL)
{
WLog_ERR(TAG, "pf_filters_register_new(): list == NULL");
goto error;
}
handle = LoadLibraryA(module_path);
if (handle == NULL)
{
WLog_ERR(TAG, "pf_filters_register_new(): failed loading external module: %s", module_path);
goto error;
}
if (!(fn = (filterInitFn) GetProcAddress(handle, FILTER_INIT_METHOD)))
{
WLog_ERR(TAG, "pf_filters_register_new(): GetProcAddress failed while loading %s", module_path);
goto error;
}
filter = (proxyFilter*) malloc(sizeof(proxyFilter));
if (filter == NULL)
{
WLog_ERR(TAG, "pf_filters_register_new(): malloc failed");
goto error;
}
events = malloc(sizeof(proxyEvents));
if (events == NULL)
{
WLog_ERR(TAG, "pf_filters_register_new(): failed loading external module: %s", module_path);
goto error;
}
if (!fn(events))
{
WLog_ERR(TAG, "pf_filters_register_new(): failed calling external filter_init: %s", module_path);
goto error;
}
filter->handle = handle;
filter->name = _strdup(filter_name);
filter->events = events;
filter->enabled = TRUE;
if (ArrayList_Add(list, filter) < 0)
{
WLog_ERR(TAG, "pf_filters_register_new(): failed adding filter to list: %s", module_path);
goto error;
}
return TRUE;
error:
if (handle)
FreeLibrary(handle);
free(events);
free(filter);
return FALSE;
}

75
server/proxy/pf_filters.h Normal file
View File

@ -0,0 +1,75 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server
*
* Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
* Copyright 2019 Idan Freiberg <speidy@gmail.com>
*
* 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_FILTERS_H
#define FREERDP_SERVER_PROXY_FILTERS_H
#include <winpr/wtypes.h>
#include <winpr/collections.h>
#include "filters/filters_api.h"
/* filter init method */
typedef BOOL (*filterInitFn)(proxyEvents* events);
typedef wArrayList filters_list;
typedef struct proxy_filter proxyFilter;
typedef enum _PF_FILTER_TYPE PF_FILTER_TYPE;
enum _PF_FILTER_TYPE
{
FILTER_TYPE_KEYBOARD,
FILTER_TYPE_MOUSE
};
struct proxy_filter
{
/* Handle to the loaded library. Used for freeing the library */
HMODULE handle;
char* name;
BOOL enabled;
proxyEvents* events;
};
BOOL pf_filters_init(filters_list** list);
BOOL pf_filters_register_new(filters_list* list, const char* module_path, const char* filter_name);
PF_FILTER_RESULT pf_filters_run_by_type(filters_list* list, PF_FILTER_TYPE type,
connectionInfo* info,
void* param);
void pf_filters_unregister_all(filters_list* list);
#define RUN_FILTER(_filters,_type,_conn_info,_event_info,_cb,...) ({ \
({ BOOL result; switch(pf_filters_run_by_type(_filters,_type,_conn_info,_event_info)) { \
case FILTER_PASS: \
result = _cb(__VA_ARGS__); \
break; \
case FILTER_IGNORE: \
result = TRUE; \
break; \
case FILTER_DROP: \
default: \
result = FALSE; \
}; result; \
}); \
})
#endif /* FREERDP_SERVER_PROXY_FILTERS_H */

View File

@ -40,7 +40,13 @@ static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
if (!config->Keyboard)
return TRUE;
return freerdp_input_send_keyboard_event(context->input, flags, code);
proxyKeyboardEventInfo info =
{
.flags = flags,
.rdp_scan_code = code
};
return RUN_FILTER(config->Filters, FILTER_TYPE_KEYBOARD, ps->pdata->info, &info,
freerdp_input_send_keyboard_event, context->input, flags, code);
}
static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
@ -66,7 +72,13 @@ static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT1
if (!config->Mouse)
return TRUE;
return freerdp_input_send_mouse_event(context->input, flags, x, y);
proxyMouseEventInfo info =
{
.flags = flags,
.x = x, .y = y
};
return RUN_FILTER(config->Filters, FILTER_TYPE_MOUSE, ps->pdata->info, &info,
freerdp_input_send_mouse_event, context->input, flags, x, y);
}
static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x,

View File

@ -158,7 +158,9 @@ static BOOL pf_server_post_connect(freerdp_peer* client)
connectionClosedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
/* keep both sides of the connection in pdata */
pc->pdata = ps->pdata;
pdata->pc = (pClientContext*) pc;
pdata->info->TargetHostname = _strdup(host);
pdata->info->Username = _strdup(client->settings->Username);
pdata->pc = pc;
pdata->ps = ps;
pdata->connectionClosed = connectionClosedEvent;
pf_server_rdpgfx_init(ps);
@ -206,7 +208,15 @@ static DWORD WINAPI pf_server_handle_client(LPVOID arg)
ps = (pServerContext*) client->context;
ps->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL);
pdata = calloc(1, sizeof(proxyData));
pdata = pf_context_proxy_data_new();
if (pdata == NULL)
{
WLog_ERR(TAG, "pf_context_proxy_data_new failed!");
return 0;
}
pdata->info->ClientHostname = _strdup(client->hostname);
ps->pdata = pdata;
/* keep configuration in proxyData */
pdata->config = client->ContextExtra;
@ -320,7 +330,7 @@ fail:
pc = (rdpContext*) pdata->pc;
freerdp_client_stop(pc);
free(pdata);
pf_context_proxy_data_free(pdata);
freerdp_client_context_free(pc);
client->Disconnect(client);
freerdp_peer_context_free(client);