/** * FreeRDP: A Remote Desktop Protocol Implementation * FreeRDP Proxy Server modules API * * 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 #include #include "pf_log.h" #include "pf_modules.h" #include "pf_context.h" #define TAG PROXY_TAG("modules") #define MODULE_ENTRY_POINT "proxy_module_entry_point" static wArrayList* plugins_list = NULL; /* list of all loaded plugins */ static wArrayList* handles_list = NULL; /* list of module handles to free at shutdown */ typedef BOOL (*moduleEntryPoint)(proxyPluginsManager* plugins_manager); static const char* FILTER_TYPE_STRINGS[] = { "KEYBOARD_EVENT", "MOUSE_EVENT", "CLIENT_CHANNEL_DATA", "SERVER_CHANNEL_DATA", "SERVER_FETCH_TARGET_ADDR" }; static const char* HOOK_TYPE_STRINGS[] = { "CLIENT_PRE_CONNECT", "CLIENT_POST_CONNECT", "CLIENT_LOGIN_FAILURE", "CLIENT_END_PAINT", "SERVER_POST_CONNECT", "SERVER_CHANNELS_INIT", "SERVER_CHANNELS_FREE", "SERVER_SESSION_END", }; static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result) { if (result >= FILTER_TYPE_KEYBOARD && result < FILTER_LAST) return FILTER_TYPE_STRINGS[result]; else return "FILTER_UNKNOWN"; } static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result) { if (result >= HOOK_TYPE_CLIENT_PRE_CONNECT && result < HOOK_LAST) return HOOK_TYPE_STRINGS[result]; else return "HOOK_UNKNOWN"; } static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; PF_HOOK_TYPE type; proxyData* pdata; BOOL ok = FALSE; type = va_arg(ap, PF_HOOK_TYPE); pdata = va_arg(ap, proxyData*); 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(plugin->ClientPreConnect, ok, pdata); break; case HOOK_TYPE_CLIENT_POST_CONNECT: IFCALLRET(plugin->ClientPostConnect, ok, pdata); break; case HOOK_TYPE_CLIENT_LOGIN_FAILURE: IFCALLRET(plugin->ClientLoginFailure, ok, pdata); break; case HOOK_TYPE_CLIENT_END_PAINT: IFCALLRET(plugin->ClientEndPaint, ok, pdata); break; case HOOK_TYPE_SERVER_POST_CONNECT: IFCALLRET(plugin->ServerPostConnect, ok, pdata); break; case HOOK_TYPE_SERVER_CHANNELS_INIT: IFCALLRET(plugin->ServerChannelsInit, ok, pdata); break; case HOOK_TYPE_SERVER_CHANNELS_FREE: IFCALLRET(plugin->ServerChannelsFree, ok, pdata); break; case HOOK_TYPE_SERVER_SESSION_END: IFCALLRET(plugin->ServerSessionEnd, ok, pdata); break; default: WLog_ERR(TAG, "invalid hook called"); } if (!ok) { WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name, pf_modules_get_hook_type_string(type)); 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, proxyData* pdata) { return ArrayList_ForEach(plugins_list, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata); } static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; PF_FILTER_TYPE type; proxyData* pdata; void* param; BOOL result = FALSE; WINPR_UNUSED(index); type = va_arg(ap, PF_FILTER_TYPE); pdata = va_arg(ap, proxyData*); param = va_arg(ap, void*); WLog_VRB(TAG, "[%s]: running filter: %s", __FUNCTION__, plugin->name); switch (type) { case FILTER_TYPE_KEYBOARD: IFCALLRET(plugin->KeyboardEvent, result, pdata, param); break; case FILTER_TYPE_MOUSE: 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; case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR: IFCALLRET(plugin->ServerFetchTargetAddr, result, pdata, param); break; default: WLog_ERR(TAG, "invalid filter called"); } if (!result) { /* current filter return FALSE, no need to run other filters. */ WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name, pf_modules_get_filter_type_string(type)); } return result; } /* * runs all filters of type `type`. * * @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, proxyData* pdata, void* param) { return ArrayList_ForEach(plugins_list, pf_modules_ArrayList_ForEachFkt, type, pdata, param); } /* * 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_plugin_data(const char* plugin_name, proxyData* pdata, void* data) { union { const char* ccp; char* cp; } ccharconv; assert(plugin_name); ccharconv.ccp = plugin_name; if (data == NULL) /* no need to store anything */ return FALSE; if (HashTable_Add(pdata->modules_info, ccharconv.cp, data) < 0) { WLog_ERR(TAG, "[%s]: HashTable_Add failed!"); return FALSE; } return TRUE; } /* * returns per-session data needed a plugin. * * @context: current session server's rdpContext instance. * if there's no data related to `plugin_name` in `context` (current session), a NULL will be * returned. */ static void* pf_modules_get_plugin_data(const char* plugin_name, proxyData* pdata) { union { const char* ccp; char* cp; } ccharconv; assert(plugin_name); assert(pdata); ccharconv.ccp = plugin_name; return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp); } static void pf_modules_abort_connect(proxyData* pdata) { assert(pdata); WLog_DBG(TAG, "%s is called!", __FUNCTION__); proxy_data_abort_connect(pdata); } static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*); WINPR_UNUSED(index); if (strcmp(plugin->name, plugin_to_register->name) == 0) { WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name); return FALSE; } return TRUE; } static BOOL pf_modules_register_plugin(proxyPlugin* plugin_to_register) { if (!plugin_to_register) return FALSE; /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */ if (!ArrayList_ForEach(plugins_list, pf_modules_register_ArrayList_ForEachFkt, plugin_to_register)) return FALSE; if (ArrayList_Add(plugins_list, plugin_to_register) < 0) { WLog_ERR(TAG, "[%s]: failed adding plugin to list: %s", __FUNCTION__, plugin_to_register->name); return FALSE; } return TRUE; } static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; const char* plugin_name = va_arg(ap, const char*); WINPR_UNUSED(index); WINPR_UNUSED(ap); if (strcmp(plugin->name, plugin_name) == 0) return TRUE; return FALSE; } BOOL pf_modules_is_plugin_loaded(const char* plugin_name) { if (plugins_list == NULL) return FALSE; return ArrayList_ForEach(plugins_list, pf_modules_load_ArrayList_ForEachFkt, plugin_name); } static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; WINPR_UNUSED(index); WINPR_UNUSED(ap); WLog_INFO(TAG, "\tName: %s", plugin->name); WLog_INFO(TAG, "\tDescription: %s", plugin->description); return TRUE; } void pf_modules_list_loaded_plugins(void) { size_t count; if (plugins_list == NULL) return; count = (size_t)ArrayList_Count(plugins_list); if (count > 0) WLog_INFO(TAG, "Loaded plugins:"); ArrayList_ForEach(plugins_list, pf_modules_print_ArrayList_ForEachFkt); } 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) { HMODULE handle = NULL; moduleEntryPoint pEntryPoint; handle = LoadLibraryA(module_path); if (handle == NULL) { WLog_ERR(TAG, "[%s]: failed loading external library: %s", __FUNCTION__, module_path); return FALSE; } if (!(pEntryPoint = (moduleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT))) { WLog_ERR(TAG, "[%s]: GetProcAddress failed while loading %s", __FUNCTION__, module_path); goto error; } if (!pEntryPoint(&plugins_manager)) { WLog_ERR(TAG, "[%s]: module %s entry point failed!", __FUNCTION__, module_path); goto error; } /* save module handle for freeing the module later */ if (ArrayList_Add(handles_list, handle) < 0) { WLog_ERR(TAG, "ArrayList_Add failed!"); return FALSE; } return TRUE; error: FreeLibrary(handle); return FALSE; } BOOL pf_modules_init(const char* root_dir, const char** modules, size_t count) { size_t i; if (!PathFileExistsA(root_dir)) { if (!CreateDirectoryA(root_dir, NULL)) { WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir); return FALSE; } return TRUE; } WLog_DBG(TAG, "modules root directory: %s", root_dir); plugins_list = ArrayList_New(FALSE); if (plugins_list == NULL) { WLog_ERR(TAG, "[%s]: ArrayList_New failed!", __FUNCTION__); goto error; } handles_list = ArrayList_New(FALSE); if (handles_list == NULL) { WLog_ERR(TAG, "[%s]: ArrayList_New failed!", __FUNCTION__); goto error; } for (i = 0; i < count; i++) { char* fullpath = GetCombinedPath(root_dir, modules[i]); pf_modules_load_module(fullpath); free(fullpath); } return TRUE; error: ArrayList_Free(plugins_list); plugins_list = NULL; ArrayList_Free(handles_list); handles_list = NULL; return FALSE; } static BOOL pf_modules_free_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { proxyPlugin* plugin = (proxyPlugin*)data; WINPR_UNUSED(index); WINPR_UNUSED(ap); if (!IFCALLRESULT(TRUE, plugin->PluginUnload)) WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name); return TRUE; } static BOOL pf_modules_free_handles_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) { HANDLE handle = (HANDLE)data; WINPR_UNUSED(index); WINPR_UNUSED(ap); if (handle) FreeLibrary(handle); return TRUE; } void pf_modules_free(void) { if (plugins_list) { ArrayList_ForEach(plugins_list, pf_modules_free_ArrayList_ForEachFkt); ArrayList_Free(plugins_list); plugins_list = NULL; } if (handles_list) { ArrayList_ForEach(handles_list, pf_modules_free_handles_ArrayList_ForEachFkt); ArrayList_Free(handles_list); handles_list = NULL; } }