diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c20267f4..91f490611 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -982,6 +982,14 @@ set(FREERDP_ADDIN_PATH "${FREERDP_PLUGIN_PATH}") # Path to put extensions set(FREERDP_EXTENSION_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/freerdp${FREERDP_VERSION_MAJOR}/extensions") +# Proxy plugins path +if(NOT DEFINED PROXY_PLUGINDIR) + message("using default plugins location") + set(FREERDP_PROXY_PLUGINDIR "${CMAKE_BINARY_DIR}/server/proxy/plugins") +else() + set(FREERDP_PROXY_PLUGINDIR "${PROXY_PLUGINDIR}") +endif() + # Include directories include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) diff --git a/include/freerdp/build-config.h.in b/include/freerdp/build-config.h.in index 261b8ded6..362429a92 100644 --- a/include/freerdp/build-config.h.in +++ b/include/freerdp/build-config.h.in @@ -17,4 +17,6 @@ #define FREERDP_VENDOR_STRING "${VENDOR}" #define FREERDP_PRODUCT_STRING "${PRODUCT}" +#define FREERDP_PROXY_PLUGINDIR "${FREERDP_PROXY_PLUGINDIR}" + #endif /* FREERDP_BUILD_CONFIG_H */ diff --git a/server/proxy/config.ini b/server/proxy/config.ini index 2bcc5a120..2fe0501aa 100644 --- a/server/proxy/config.ini +++ b/server/proxy/config.ini @@ -40,5 +40,5 @@ RemoteApp = TRUE TextOnly = FALSE MaxTextLength = 10 # 0 for no limit. -[Modules] -Demo = "modules/demo/libdemo.so" +[Plugins] +Required = "demo" diff --git a/server/proxy/freerdp_proxy.c b/server/proxy/freerdp_proxy.c index df60aa762..a90b81f80 100644 --- a/server/proxy/freerdp_proxy.c +++ b/server/proxy/freerdp_proxy.c @@ -24,6 +24,7 @@ #include "pf_log.h" #include "pf_modules.h" +#include #include #define TAG PROXY_TAG("server") @@ -40,8 +41,13 @@ int main(int argc, char* argv[]) if (argc > 1) cfg = argv[1]; - if (!pf_modules_init()) + if (!pf_modules_init(FREERDP_PROXY_PLUGINDIR)) + { + WLog_ERR(TAG, "failed to initialize proxy plugins!"); goto fail; + } + + pf_modules_list_loaded_plugins(); if (!pf_server_config_load(cfg, config)) goto fail; diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt index 849461e5a..ceadab425 100644 --- a/server/proxy/modules/demo/CMakeLists.txt +++ b/server/proxy/modules/demo/CMakeLists.txt @@ -1,4 +1,29 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2019 Kobi Mizrachi +# +# 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. +# -add_library(demo SHARED - demo.c +set(PLUGIN_NAME "proxy-demo-plugin") + +add_library(${PLUGIN_NAME} MODULE + demo.cpp ) + +set_target_properties(${PLUGIN_NAME} PROPERTIES PREFIX "") +set_target_properties(${PLUGIN_NAME} PROPERTIES NO_SONAME 1) +set_target_properties(${PLUGIN_NAME} PROPERTIES +LIBRARY_OUTPUT_DIRECTORY "${FREERDP_PROXY_PLUGINDIR}") diff --git a/server/proxy/modules/demo/demo.c b/server/proxy/modules/demo/demo.c deleted file mode 100644 index 0c700305e..000000000 --- a/server/proxy/modules/demo/demo.c +++ /dev/null @@ -1,57 +0,0 @@ -/** - * FreeRDP: A Remote Desktop Protocol Implementation - * FreeRDP Proxy Server - * - * Copyright 2019 Kobi Mizrachi - * - * 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 "modules_api.h" - -static BOOL demo_filter_keyboard_event(moduleOperations* module, rdpContext* context, void* param) -{ - proxyKeyboardEventInfo* event_data = (proxyKeyboardEventInfo*)param; - WINPR_UNUSED(event_data); - - return TRUE; -} - -static BOOL demo_filter_mouse_event(moduleOperations* module, rdpContext* context, void* param) -{ - proxyMouseEventInfo* event_data = (proxyMouseEventInfo*)param; - - if (event_data->x % 100 == 0) - { - printf("filter_demo: mouse x is currently %" PRIu16 "\n", event_data->x); - } - - return TRUE; -} - -BOOL module_init(moduleOperations* module) -{ - module->KeyboardEvent = demo_filter_keyboard_event; - module->MouseEvent = demo_filter_mouse_event; - - return TRUE; -} - -BOOL module_exit(moduleOperations* module) -{ - printf("bye bye\n"); - - return TRUE; -} diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp new file mode 100644 index 000000000..f105e29ad --- /dev/null +++ b/server/proxy/modules/demo/demo.cpp @@ -0,0 +1,71 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server Demo C++ Module + * + * Copyright 2019 Kobi Mizrachi + * + * 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 "modules_api.h" + +#define TAG MODULE_TAG("demo") + +static constexpr char plugin_name[] = "demo"; +static constexpr char plugin_desc[] = "this is a test plugin"; + +static proxyPluginsManager* g_plugins_manager = NULL; + +static BOOL demo_filter_keyboard_event(proxyData* pdata, void* param) +{ + auto event_data = static_cast(param); + if (event_data == NULL) + return FALSE; + + if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B) + { + /* user typed 'B', that means bye :) */ + std::cout << "C++ demo plugin: aborting connection" << std::endl; + g_plugins_manager->AbortConnect(pdata); + } + + return TRUE; +} + +static BOOL demo_plugin_unload() +{ + std::cout << "C++ demo plugin: unloading..." << std::endl; + return TRUE; +} + +static proxyPlugin demo_plugin = { + plugin_name, /* name */ + plugin_desc, /* description */ + NULL, /* ClientPreConnect */ + NULL, /* ClientLoginFailure */ + NULL, /* ServerPostConnect */ + NULL, /* ServerChannelsInit */ + NULL, /* ServerChannelsFree */ + demo_filter_keyboard_event, /* KeyboardEvent */ + NULL, /* MouseEvent */ + demo_plugin_unload /* PluginUnload */ +}; + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager) +{ + g_plugins_manager = plugins_manager; + + return plugins_manager->RegisterPlugin(&demo_plugin); +} diff --git a/server/proxy/modules/modules_api.h b/server/proxy/modules/modules_api.h index 375956297..7330169ea 100644 --- a/server/proxy/modules/modules_api.h +++ b/server/proxy/modules/modules_api.h @@ -24,78 +24,89 @@ #include #include -#define PROXY_API FREERDP_API +#include "../pf_context.h" -typedef struct module_operations moduleOperations; +#define MODULE_TAG(module) "proxy.modules." module -/* used for filtering */ -typedef BOOL (*proxyFilterFn)(moduleOperations*, rdpContext*, void*); - -/* used for hooks */ -typedef BOOL (*proxyHookFn)(moduleOperations*, rdpContext*); +/* hook callback. should return TRUE on success or FALSE on error. */ +typedef BOOL (*proxyHookFn)(proxyData*); /* - * used for per-session info. - * - * each module is allowed to store data per session. - * it is useful, for example, when a module wants to create a server channel in runtime, - * or to save information about a session (for example, the count of mouse clicks in the last - * minute), and then do something with this information (maybe abort the connection). + * Filter callback: + * It MUST return TRUE if the related event should be proxied, + * or FALSE if it should be ignored. */ -typedef BOOL (*moduleSetSessionData)(moduleOperations*, rdpContext*, void*); -typedef void* (*moduleGetSessionData)(moduleOperations*, rdpContext*); +typedef BOOL (*proxyFilterFn)(proxyData*, void*); -/* - * used for connection management. when a module wants to forcibly close a connection, it should - * call this method. - */ -typedef void (*moduleAbortConnect)(moduleOperations*, rdpContext*); - -typedef struct connection_info connectionInfo; -typedef struct proxy_keyboard_event_info proxyKeyboardEventInfo; -typedef struct proxy_mouse_event_info proxyMouseEventInfo; - -/* represents a set of operations that a module can do */ -struct module_operations +/* describes a plugin: name, description and callbacks to execute. */ +typedef struct proxy_plugin { - /* per-session API. a module must not change these function pointers. */ - moduleSetSessionData SetSessionData; - moduleGetSessionData GetSessionData; - moduleAbortConnect AbortConnect; + const char* name; /* unique module name */ + const char* description; /* module description */ - /* proxy hooks. a module can set these function pointers to register hooks. */ + /* proxy hooks. a module can set these function pointers to register hooks */ proxyHookFn ClientPreConnect; + proxyHookFn ClientLoginFailure; + proxyHookFn ServerPostConnect; proxyHookFn ServerChannelsInit; proxyHookFn ServerChannelsFree; - /* proxy filters a module can set these function pointers to register filters. */ + /* proxy filters. a module can set these function pointers to register filters */ proxyFilterFn KeyboardEvent; proxyFilterFn MouseEvent; -}; + + BOOL (*PluginUnload)(); +} proxyPlugin; + +/* + * Main API for use by external modules. + * Supports: + * - Registering a plugin. + * - Setting/getting plugin's per-session specific data. + * - Aborting a session. + */ +typedef struct proxy_plugins_manager +{ + /* used for registering a fresh new proxy plugin. */ + BOOL (*RegisterPlugin)(proxyPlugin* plugin); + + /* used for setting plugin's per-session info. */ + BOOL (*SetPluginData)(const char*, proxyData*, void*); + + /* used for getting plugin's per-session info. */ + void* (*GetPluginData)(const char*, proxyData*); + + /* used for aborting a session. */ + void (*AbortConnect)(proxyData*); +} proxyPluginsManager; /* filter events parameters */ #define WINPR_PACK_PUSH #include -struct proxy_keyboard_event_info +typedef struct proxy_keyboard_event_info { UINT16 flags; UINT16 rdp_scan_code; -}; +} proxyKeyboardEventInfo; -struct proxy_mouse_event_info +typedef struct proxy_mouse_event_info { UINT16 flags; UINT16 x; UINT16 y; -}; +} proxyMouseEventInfo; #define WINPR_PACK_POP #include -/* - * these two functions must be implemented by any proxy module. - * module_init: used for module initialization, hooks and filters registration. - */ -PROXY_API BOOL module_init(moduleOperations* module); -PROXY_API BOOL module_exit(moduleOperations* module); +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager); + +#ifdef __cplusplus +}; +#endif #endif /* FREERDP_SERVER_PROXY_MODULES_API_H */ diff --git a/server/proxy/pf_channels.c b/server/proxy/pf_channels.c index 073380b36..08fd96572 100644 --- a/server/proxy/pf_channels.c +++ b/server/proxy/pf_channels.c @@ -230,7 +230,7 @@ BOOL pf_server_channels_init(pServerContext* ps) return FALSE; } - return pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_INIT, context); + return pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_INIT, ps->pdata); } void pf_server_channels_free(pServerContext* ps) @@ -265,5 +265,5 @@ void pf_server_channels_free(pServerContext* ps) ps->rail = NULL; } - pf_modules_run_hook(HOOK_TYPE_SERVER_CHANNELS_FREE, (rdpContext*)ps); + 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 8c9f8731a..c834c9d18 100644 --- a/server/proxy/pf_client.c +++ b/server/proxy/pf_client.c @@ -343,6 +343,7 @@ static BOOL pf_client_connect(freerdp* instance) goto out; LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA"); + pf_modules_run_hook(HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata); if (!pf_client_connect_without_nla(pc)) { @@ -366,7 +367,6 @@ static DWORD WINAPI pf_client_thread_proc(LPVOID arg) { freerdp* instance = (freerdp*)arg; pClientContext* pc = (pClientContext*)instance->context; - pServerContext* ps = pc->pdata->ps; proxyData* pdata = pc->pdata; DWORD nCount; DWORD status; @@ -381,7 +381,7 @@ static DWORD WINAPI pf_client_thread_proc(LPVOID arg) */ handles[64] = pdata->abort_event; - if (!pf_modules_run_hook(HOOK_TYPE_CLIENT_PRE_CONNECT, (rdpContext*)ps)) + if (!pf_modules_run_hook(HOOK_TYPE_CLIENT_PRE_CONNECT, pdata)) { proxy_data_abort_connect(pdata); return FALSE; @@ -399,7 +399,7 @@ static DWORD WINAPI pf_client_thread_proc(LPVOID arg) if (nCount == 0) { - LOG_ERR(TAG, ps, "freerdp_get_event_handles failed!"); + LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!"); break; } diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c index b4112757f..b208e82af 100644 --- a/server/proxy/pf_config.c +++ b/server/proxy/pf_config.c @@ -99,7 +99,7 @@ const char* pf_config_get_str(wIniFile* ini, const char* section, const char* ke if (!value) { - WLog_ERR(TAG, "[%s]: key '%s.%s' not found.", __FUNCTION__, key, section); + WLog_ERR(TAG, "[%s]: key '%s.%s' not found.", __FUNCTION__, section, key); return NULL; } @@ -188,31 +188,27 @@ static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config) static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config) { - int index; - int modules_count = 0; - char** module_names = NULL; + const char* required_modules; + char* tmp; - module_names = IniFile_GetSectionKeyNames(ini, "Modules", &modules_count); + /* make sure that all required modules are loaded */ + required_modules = IniFile_GetKeyValueString(ini, "Plugins", "Required"); + if (!required_modules) + return TRUE; - for (index = 0; index < modules_count; index++) + tmp = strtok((char*)required_modules, ","); + + while (tmp != NULL) { - char* module_name = module_names[index]; - const char* path = pf_config_get_str(ini, "Modules", module_name); - - if (!path) - continue; - - if (!pf_modules_register_new(path, module_name)) + if (!pf_modules_is_plugin_loaded(tmp)) { - WLog_ERR(TAG, "pf_config_load_modules(): failed to register %s (%s)", module_name, - path); - continue; + WLog_ERR(TAG, "Required plugin '%s' is not loaded. stopping.", tmp); + return FALSE; } - WLog_INFO(TAG, "module '%s' is loaded!", module_name); + tmp = strtok(NULL, ","); } - free(module_names); return TRUE; } diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c index 76e1a0271..ac002b3c6 100644 --- a/server/proxy/pf_context.c +++ b/server/proxy/pf_context.c @@ -29,11 +29,6 @@ static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* context) { context->dynvcReady = NULL; - context->modules_info = NULL; - - context->modules_info = HashTable_New(TRUE); - if (!context->modules_info) - return FALSE; context->vcm = WTSOpenServerA((LPSTR)client->context); @@ -46,7 +41,6 @@ static BOOL client_to_proxy_context_new(freerdp_peer* client, pServerContext* co return TRUE; error: - HashTable_Free(context->modules_info); WTSCloseServer((HANDLE)context->vcm); context->vcm = NULL; @@ -74,8 +68,6 @@ static void client_to_proxy_context_free(freerdp_peer* client, pServerContext* c CloseHandle(context->dynvcReady); context->dynvcReady = NULL; } - - HashTable_Free(context->modules_info); } BOOL pf_context_init_server_context(freerdp_peer* client) @@ -198,6 +190,17 @@ proxyData* proxy_data_new(void) return NULL; } + if (!(pdata->modules_info = HashTable_New(FALSE))) + { + proxy_data_free(pdata); + return NULL; + } + + /* modules_info maps between plugin name to custom data */ + pdata->modules_info->hash = HashTable_StringHash; + pdata->modules_info->keyCompare = HashTable_StringCompare; + pdata->modules_info->keyClone = HashTable_StringClone; + pdata->modules_info->keyFree = HashTable_StringFree; return pdata; } @@ -238,6 +241,9 @@ void proxy_data_free(proxyData* pdata) if (pdata->session_id) free(pdata->session_id); + if (pdata->modules_info) + HashTable_Free(pdata->modules_info); + free(pdata); } diff --git a/server/proxy/pf_context.h b/server/proxy/pf_context.h index 201e406e8..597c84c2f 100644 --- a/server/proxy/pf_context.h +++ b/server/proxy/pf_context.h @@ -56,9 +56,6 @@ struct p_server_context DispServerContext* disp; CliprdrServerContext* cliprdr; RdpsndServerContext* rdpsnd; - - /* used to external modules to store per-session info */ - wHashTable* modules_info; }; typedef struct p_server_context pServerContext; @@ -110,6 +107,9 @@ struct proxy_data HANDLE gfx_server_ready; char* session_id; + + /* used to external modules to store per-session info */ + wHashTable* modules_info; }; BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src); diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c index 156548db5..2a9baaa91 100644 --- a/server/proxy/pf_input.c +++ b/server/proxy/pf_input.c @@ -43,7 +43,7 @@ static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) event.flags = flags; event.rdp_scan_code = code; - if (pf_modules_run_filter(FILTER_TYPE_KEYBOARD, input->context, &event)) + if (pf_modules_run_filter(FILTER_TYPE_KEYBOARD, pc->pdata, &event)) return freerdp_input_send_keyboard_event(pc->context.input, flags, code); return TRUE; @@ -75,7 +75,7 @@ static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT1 event.x = x; event.y = y; - if (pf_modules_run_filter(FILTER_TYPE_MOUSE, input->context, &event)) + if (pf_modules_run_filter(FILTER_TYPE_MOUSE, pc->pdata, &event)) return freerdp_input_send_mouse_event(pc->context.input, flags, x, y); return TRUE; diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c index f4e8278ad..fa528bcda 100644 --- a/server/proxy/pf_modules.c +++ b/server/proxy/pf_modules.c @@ -20,9 +20,11 @@ #include +#include #include #include #include +#include #include "pf_log.h" #include "pf_modules.h" @@ -30,14 +32,12 @@ #define TAG PROXY_TAG("modules") -#define MODULE_INIT_METHOD "module_init" -#define MODULE_EXIT_METHOD "module_exit" +#define MODULE_ENTRY_POINT "proxy_module_entry_point" -static modules_list* proxy_modules = NULL; +static wArrayList* plugins_list = NULL; /* list of all loaded plugins */ +static wArrayList* handles_list = NULL; /* list of module handles to free at shutdown */ -/* module init/exit methods */ -typedef BOOL (*moduleInitFn)(moduleOperations* ops); -typedef BOOL (*moduleExitFn)(moduleOperations* ops); +typedef BOOL (*moduleEntryPoint)(proxyPluginsManager* plugins_manager); static const char* FILTER_TYPE_STRINGS[] = { "KEYBOARD_EVENT", @@ -45,14 +45,13 @@ static const char* FILTER_TYPE_STRINGS[] = { }; static const char* HOOK_TYPE_STRINGS[] = { - "CLIENT_PRE_CONNECT", - "SERVER_CHANNELS_INIT", - "SERVER_CHANNELS_FREE", + "CLIENT_PRE_CONNECT", "CLIENT_LOGIN_FAILURE", "SERVER_POST_CONNECT", + "SERVER_CHANNELS_INIT", "SERVER_CHANNELS_FREE", }; static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result) { - if (result >= FILTER_TYPE_KEYBOARD && result <= FILTER_TYPE_MOUSE) + if (result >= FILTER_TYPE_KEYBOARD && result < FILTER_LAST) return FILTER_TYPE_STRINGS[result]; else return "FILTER_UNKNOWN"; @@ -60,65 +59,57 @@ static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result) static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result) { - if (result >= HOOK_TYPE_CLIENT_PRE_CONNECT && result <= HOOK_TYPE_SERVER_CHANNELS_FREE) + if (result >= HOOK_TYPE_CLIENT_PRE_CONNECT && result < HOOK_LAST) return HOOK_TYPE_STRINGS[result]; else return "HOOK_UNKNOWN"; } -BOOL pf_modules_init(void) -{ - proxy_modules = ArrayList_New(FALSE); - - if (proxy_modules == NULL) - { - WLog_ERR(TAG, "pf_modules_init(): ArrayList_New failed!"); - return FALSE; - } - - return TRUE; -} - /* * runs all hooks of type `type`. * * @type: hook type to run. * @server: pointer of server's rdpContext struct of the current session. */ -BOOL pf_modules_run_hook(PF_HOOK_TYPE type, rdpContext* context) +BOOL pf_modules_run_hook(PF_HOOK_TYPE type, proxyData* pdata) { - - proxyModule* module; - moduleOperations* ops; BOOL ok = TRUE; - const size_t count = (size_t)ArrayList_Count(proxy_modules); size_t index; + proxyPlugin* plugin; - for (index = 0; index < count; index++) + ArrayList_ForEach(plugins_list, proxyPlugin*, index, plugin) { - module = (proxyModule*)ArrayList_GetItem(proxy_modules, index); - ops = module->ops; - WLog_VRB(TAG, "[%s]: Running module %s, hook %s", __FUNCTION__, module->name, - pf_modules_get_hook_type_string(type)); + WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type)); switch (type) { case HOOK_TYPE_CLIENT_PRE_CONNECT: - IFCALLRET(ops->ClientPreConnect, ok, ops, context); + IFCALLRET(plugin->ClientPreConnect, ok, pdata); + break; + + case HOOK_TYPE_CLIENT_LOGIN_FAILURE: + IFCALLRET(plugin->ClientLoginFailure, ok, pdata); + break; + + case HOOK_TYPE_SERVER_POST_CONNECT: + IFCALLRET(plugin->ServerPostConnect, ok, pdata); break; case HOOK_TYPE_SERVER_CHANNELS_INIT: - IFCALLRET(ops->ServerChannelsInit, ok, ops, context); + IFCALLRET(plugin->ServerChannelsInit, ok, pdata); break; case HOOK_TYPE_SERVER_CHANNELS_FREE: - IFCALLRET(ops->ServerChannelsFree, ok, ops, context); + IFCALLRET(plugin->ServerChannelsFree, ok, pdata); break; + + default: + WLog_ERR(TAG, "invalid hook called"); } if (!ok) { - WLog_INFO(TAG, "Module %s, hook %s failed!", module->name, + WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name, pf_modules_get_hook_type_string(type)); return FALSE; } @@ -133,35 +124,34 @@ BOOL pf_modules_run_hook(PF_HOOK_TYPE type, rdpContext* context) * @type: filter type to run. * @server: pointer of server's rdpContext struct of the current session. */ -BOOL pf_modules_run_filter(PF_FILTER_TYPE type, rdpContext* server, void* param) +BOOL pf_modules_run_filter(PF_FILTER_TYPE type, proxyData* pdata, void* param) { - proxyModule* module; - moduleOperations* ops; BOOL result = TRUE; - const size_t count = (size_t)ArrayList_Count(proxy_modules); size_t index; + proxyPlugin* plugin; - for (index = 0; index < count; index++) + ArrayList_ForEach(plugins_list, proxyPlugin*, index, plugin) { - module = (proxyModule*)ArrayList_GetItem(proxy_modules, index); - ops = module->ops; - WLog_VRB(TAG, "[%s]: running filter: %s", __FUNCTION__, module->name); + WLog_VRB(TAG, "[%s]: running filter: %s", __FUNCTION__, plugin->name); switch (type) { case FILTER_TYPE_KEYBOARD: - IFCALLRET(ops->KeyboardEvent, result, ops, server, param); + IFCALLRET(plugin->KeyboardEvent, result, pdata, param); break; case FILTER_TYPE_MOUSE: - IFCALLRET(ops->MouseEvent, result, ops, server, param); + IFCALLRET(plugin->MouseEvent, result, pdata, param); break; + + default: + WLog_ERR(TAG, "invalid filter called"); } if (!result) { /* current filter return FALSE, no need to run other filters. */ - WLog_INFO(TAG, "module %s, filter type [%s] returned FALSE", module->name, + WLog_INFO(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name, pf_modules_get_filter_type_string(type)); return result; } @@ -171,73 +161,20 @@ BOOL pf_modules_run_filter(PF_FILTER_TYPE type, rdpContext* server, void* param) return TRUE; } -static void pf_modules_module_free(proxyModule* module) -{ - moduleExitFn exitFn; - - assert(module); - assert(module->handle); - - exitFn = (moduleExitFn)GetProcAddress(module->handle, MODULE_EXIT_METHOD); - - if (!exitFn) - { - WLog_ERR(TAG, "[%s]: GetProcAddress module_exit for %s failed!", __FUNCTION__, - module->name); - } - else - { - if (!exitFn(module->ops)) - { - WLog_ERR(TAG, "[%s]: module_exit failed for %s!", __FUNCTION__, module->name); - } - } - - FreeLibrary(module->handle); - module->handle = NULL; - - free(module->name); - free(module->ops); - free(module); -} - -void pf_modules_free(void) -{ - size_t index, count; - - if (proxy_modules == NULL) - return; - - count = (size_t)ArrayList_Count(proxy_modules); - - for (index = 0; index < count; index++) - { - proxyModule* module = (proxyModule*)ArrayList_GetItem(proxy_modules, index); - WLog_INFO(TAG, "[%s]: freeing module: %s", __FUNCTION__, module->name); - pf_modules_module_free(module); - } - - ArrayList_Free(proxy_modules); -} - /* - * stores per-session data needed by module. + * stores per-session data needed by a plugin. * * @context: current session server's rdpContext instance. * @info: pointer to per-session data. */ -static BOOL pf_modules_set_session_data(moduleOperations* module, rdpContext* context, void* data) +static BOOL pf_modules_set_plugin_data(const char* plugin_name, proxyData* pdata, void* data) { - pServerContext* ps; - - assert(module); - assert(context); + assert(plugin_name); if (data == NULL) /* no need to store anything */ return FALSE; - ps = (pServerContext*)context; - if (HashTable_Add(ps->modules_info, (void*)module, data) < 0) + if (HashTable_Add(pdata->modules_info, (void*)plugin_name, data) < 0) { WLog_ERR(TAG, "[%s]: HashTable_Add failed!"); return FALSE; @@ -247,104 +184,215 @@ static BOOL pf_modules_set_session_data(moduleOperations* module, rdpContext* co } /* - * returns per-session data needed by module. + * returns per-session data needed a plugin. * * @context: current session server's rdpContext instance. - * if there's no data related to `module` in `context` (current session), a NULL will be returned. + * if there's no data related to `plugin_name` in `context` (current session), a NULL will be + * returned. */ -static void* pf_modules_get_session_data(moduleOperations* module, rdpContext* context) +static void* pf_modules_get_plugin_data(const char* plugin_name, proxyData* pdata) { - pServerContext* ps; + assert(plugin_name); + assert(pdata); - assert(module); - assert(context); - - ps = (pServerContext*)context; - return HashTable_GetItemValue(ps->modules_info, module); + return HashTable_GetItemValue(pdata->modules_info, (void*)plugin_name); } -static void pf_modules_abort_connect(moduleOperations* module, rdpContext* context) +static void pf_modules_abort_connect(proxyData* pdata) { - pServerContext* ps; - - assert(module); - assert(context); - - WLog_INFO(TAG, "%s is called!", __FUNCTION__); - - ps = (pServerContext*)context; - proxy_data_abort_connect(ps->pdata); + assert(pdata); + WLog_DBG(TAG, "%s is called!", __FUNCTION__); + proxy_data_abort_connect(pdata); } -BOOL pf_modules_register_new(const char* module_path, const char* module_name) +static BOOL pf_modules_register_plugin(proxyPlugin* plugin_to_register) +{ + size_t index; + proxyPlugin* plugin; + + assert(plugins_list != NULL); + + /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */ + ArrayList_ForEach(plugins_list, proxyPlugin*, index, plugin) + { + if (strcmp(plugin->name, plugin_to_register->name) == 0) + { + WLog_ERR(TAG, "can not register plugin '%s', it is already registered!"); + return FALSE; + } + } + + if (ArrayList_Add(plugins_list, plugin_to_register) < 0) + { + WLog_ERR(TAG, "[%s]: failed adding plugin to list: %s", __FUNCTION__, plugin->name); + return FALSE; + } + + return TRUE; +} + +BOOL pf_modules_is_plugin_loaded(const char* plugin_name) +{ + size_t i; + proxyPlugin* plugin; + + if (plugins_list == NULL) + return FALSE; + + ArrayList_ForEach(plugins_list, proxyPlugin*, i, plugin) + { + if (strcmp(plugin->name, plugin_name) == 0) + return TRUE; + } + + return FALSE; +} + +void pf_modules_list_loaded_plugins(void) +{ + size_t count; + size_t i; + proxyPlugin* plugin; + + if (plugins_list == NULL) + return; + + count = (size_t)ArrayList_Count(plugins_list); + + if (count > 0) + WLog_INFO(TAG, "Loaded plugins:"); + + ArrayList_ForEach(plugins_list, proxyPlugin*, i, plugin) + { + + WLog_INFO(TAG, "\tName: %s", plugin->name); + WLog_INFO(TAG, "\tDescription: %s", plugin->description); + } +} + +static proxyPluginsManager plugins_manager = { pf_modules_register_plugin, + pf_modules_set_plugin_data, + pf_modules_get_plugin_data, + pf_modules_abort_connect }; + +static BOOL pf_modules_load_module(const char* module_path) { - moduleOperations* ops = NULL; - proxyModule* module = NULL; HMODULE handle = NULL; - moduleInitFn fn; - - assert(proxy_modules != NULL); + moduleEntryPoint pEntryPoint; handle = LoadLibraryA(module_path); if (handle == NULL) { - WLog_ERR(TAG, "pf_modules_register_new(): failed loading external module: %s", module_path); + WLog_ERR(TAG, "[%s]: failed loading external library: %s", __FUNCTION__, module_path); return FALSE; } - if (!(fn = (moduleInitFn)GetProcAddress(handle, MODULE_INIT_METHOD))) + if (!(pEntryPoint = (moduleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT))) { - WLog_ERR(TAG, "pf_modules_register_new(): GetProcAddress failed while loading %s", - module_path); + WLog_ERR(TAG, "[%s]: GetProcAddress failed while loading %s", __FUNCTION__, module_path); goto error; } - module = (proxyModule*)calloc(1, sizeof(proxyModule)); - - if (module == NULL) + if (!pEntryPoint(&plugins_manager)) { - WLog_ERR(TAG, "pf_modules_register_new(): malloc failed"); + WLog_ERR(TAG, "[%s]: module %s entry point failed!", __FUNCTION__, module_path); goto error; } - ops = calloc(1, sizeof(moduleOperations)); - - if (ops == NULL) + /* save module handle for freeing the module later */ + if (ArrayList_Add(handles_list, handle) < 0) { - WLog_ERR(TAG, "pf_modules_register_new(): calloc moduleOperations failed"); - goto error; - } - - ops->AbortConnect = pf_modules_abort_connect; - ops->SetSessionData = pf_modules_set_session_data; - ops->GetSessionData = pf_modules_get_session_data; - - if (!fn(ops)) - { - WLog_ERR(TAG, "pf_modules_register_new(): failed to initialize module %s", module_path); - goto error; - } - - module->name = _strdup(module_name); - if (!module->name) - { - WLog_ERR(TAG, "pf_modules_register_new(): _strdup failed while loading %s", module_path); - goto error; - } - - module->handle = handle; - module->ops = ops; - module->enabled = TRUE; - - if (ArrayList_Add(proxy_modules, module) < 0) - { - WLog_ERR(TAG, "pf_modules_register_new(): failed adding module to list: %s", module_path); - goto error; + WLog_ERR(TAG, "ArrayList_Add failed!"); + return FALSE; } return TRUE; error: - pf_modules_module_free(module); + FreeLibrary(handle); return FALSE; -} \ No newline at end of file +} + +BOOL pf_modules_init(const char* modules_directory) +{ + WIN32_FIND_DATA ffd; + HANDLE hFind = INVALID_HANDLE_VALUE; + + WLog_DBG(TAG, "Searching plugins in directory %s", modules_directory); + + hFind = FindFirstFile(modules_directory, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) + { + WLog_ERR(TAG, "FindFirstFile failed!"); + return FALSE; + } + + plugins_list = ArrayList_New(FALSE); + + if (plugins_list == NULL) + { + WLog_ERR(TAG, "[%s]: ArrayList_New failed!", __FUNCTION__); + return FALSE; + } + + handles_list = ArrayList_New(FALSE); + if (handles_list == NULL) + { + ArrayList_Free(plugins_list); + plugins_list = NULL; + + WLog_ERR(TAG, "[%s]: ArrayList_New failed!", __FUNCTION__); + return FALSE; + } + + do + { + if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + { + char* fullpath = GetCombinedPath(modules_directory, ffd.cFileName); + char* dot = strrchr(ffd.cFileName, '.'); + + if (dot && strcmp(dot, FREERDP_SHARED_LIBRARY_SUFFIX) == 0) + pf_modules_load_module(fullpath); + + free(fullpath); + } + } while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + return TRUE; +} + +void pf_modules_free(void) +{ + size_t index; + + if (plugins_list) + { + proxyPlugin* plugin; + + ArrayList_ForEach(plugins_list, proxyPlugin*, index, plugin) + { + if (!IFCALLRESULT(TRUE, plugin->PluginUnload)) + WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name); + } + + ArrayList_Free(plugins_list); + plugins_list = NULL; + } + + if (handles_list) + { + HANDLE handle; + + ArrayList_ForEach(handles_list, HANDLE, index, handle) + { + if (handle) + FreeLibrary(handle); + }; + + ArrayList_Free(handles_list); + handles_list = NULL; + } +} diff --git a/server/proxy/pf_modules.h b/server/proxy/pf_modules.h index e6b6911c4..b2f1fe0f2 100644 --- a/server/proxy/pf_modules.h +++ b/server/proxy/pf_modules.h @@ -26,39 +26,34 @@ #include "modules/modules_api.h" -typedef wArrayList modules_list; -typedef struct proxy_module proxyModule; - typedef enum _PF_FILTER_TYPE PF_FILTER_TYPE; enum _PF_FILTER_TYPE { FILTER_TYPE_KEYBOARD, - FILTER_TYPE_MOUSE + FILTER_TYPE_MOUSE, + + FILTER_LAST }; typedef enum _PF_HOOK_TYPE PF_HOOK_TYPE; enum _PF_HOOK_TYPE { HOOK_TYPE_CLIENT_PRE_CONNECT, + HOOK_TYPE_CLIENT_LOGIN_FAILURE, + + HOOK_TYPE_SERVER_POST_CONNECT, HOOK_TYPE_SERVER_CHANNELS_INIT, HOOK_TYPE_SERVER_CHANNELS_FREE, + + HOOK_LAST }; -struct proxy_module -{ - /* Handle to the loaded library. Used for freeing the library */ - HMODULE handle; +BOOL pf_modules_init(const char* modules_directory_path); +BOOL pf_modules_is_plugin_loaded(const char* plugin_name); +void pf_modules_list_loaded_plugins(void); - char* name; - BOOL enabled; - moduleOperations* ops; -}; - -BOOL pf_modules_init(void); -BOOL pf_modules_register_new(const char* module_path, const char* module_name); - -BOOL pf_modules_run_filter(PF_FILTER_TYPE type, rdpContext* server, void* param); -BOOL pf_modules_run_hook(PF_HOOK_TYPE type, rdpContext* context); +BOOL pf_modules_run_filter(PF_FILTER_TYPE type, proxyData* pdata, void* param); +BOOL pf_modules_run_hook(PF_HOOK_TYPE type, proxyData* pdata); void pf_modules_free(void); diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c index 10e8c054e..696eac9f8 100644 --- a/server/proxy/pf_server.c +++ b/server/proxy/pf_server.c @@ -51,6 +51,7 @@ #include "pf_disp.h" #include "pf_rail.h" #include "pf_channels.h" +#include "pf_modules.h" #define TAG PROXY_TAG("server") @@ -189,7 +190,7 @@ static BOOL pf_server_post_connect(freerdp_peer* peer) pf_server_register_input_callbacks(peer->input); pf_server_register_update_callbacks(peer->update); - return TRUE; + return pf_modules_run_hook(HOOK_TYPE_SERVER_POST_CONNECT, pdata); } static BOOL pf_server_activate(freerdp_peer* peer)