diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt index 6d749f577..46dc1208b 100644 --- a/server/proxy/CMakeLists.txt +++ b/server/proxy/CMakeLists.txt @@ -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. diff --git a/server/proxy/config.ini b/server/proxy/config.ini index 3a04dc6d1..c52804141 100644 --- a/server/proxy/config.ini +++ b/server/proxy/config.ini @@ -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" diff --git a/server/proxy/filters/filter_demo.c b/server/proxy/filters/filter_demo.c new file mode 100644 index 000000000..985dbcf85 --- /dev/null +++ b/server/proxy/filters/filter_demo.c @@ -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; +} diff --git a/server/proxy/filters/filters_api.h b/server/proxy/filters/filters_api.h new file mode 100644 index 000000000..c77386d42 --- /dev/null +++ b/server/proxy/filters/filters_api.h @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2019 Mati Shabtay + * Copyright 2019 Kobi Mizrachi + * Copyright 2019 Idan Freiberg + * + * 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 +#include + +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 */ diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c index 388a1e5bb..88841e60b 100644 --- a/server/proxy/pf_config.c +++ b/server/proxy/pf_config.c @@ -22,6 +22,8 @@ #include #include #include +#include + #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); diff --git a/server/proxy/pf_config.h b/server/proxy/pf_config.h index 3ae229977..26429a1d6 100644 --- a/server/proxy/pf_config.h +++ b/server/proxy/pf_config.h @@ -28,6 +28,8 @@ #include +#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; diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c index 0e00d4039..b8c516b2b 100644 --- a/server/proxy/pf_context.c +++ b/server/proxy/pf_context.c @@ -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); +} diff --git a/server/proxy/pf_context.h b/server/proxy/pf_context.h index fa92168de..ae9856fc6 100644 --- a/server/proxy/pf_context.h +++ b/server/proxy/pf_context.h @@ -29,6 +29,7 @@ #include #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 */ diff --git a/server/proxy/pf_filters.c b/server/proxy/pf_filters.c new file mode 100644 index 000000000..171cb4aaf --- /dev/null +++ b/server/proxy/pf_filters.c @@ -0,0 +1,218 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2019 Mati Shabtay + * Copyright 2019 Kobi Mizrachi + * Copyright 2019 Idan Freiberg + * + * 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 + +#include +#include +#include + +#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; +} diff --git a/server/proxy/pf_filters.h b/server/proxy/pf_filters.h new file mode 100644 index 000000000..8924bcade --- /dev/null +++ b/server/proxy/pf_filters.h @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2019 Mati Shabtay + * Copyright 2019 Kobi Mizrachi + * Copyright 2019 Idan Freiberg + * + * 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 +#include + +#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 */ diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c index 6babb2b5d..396129224 100644 --- a/server/proxy/pf_input.c +++ b/server/proxy/pf_input.c @@ -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, diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c index c9e933fca..e85b4da49 100644 --- a/server/proxy/pf_server.c +++ b/server/proxy/pf_server.c @@ -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);