summaryrefslogtreecommitdiffstats
path: root/server/proxy/pf_modules.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/proxy/pf_modules.c')
-rw-r--r--server/proxy/pf_modules.c633
1 files changed, 633 insertions, 0 deletions
diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c
new file mode 100644
index 0000000..e3447cb
--- /dev/null
+++ b/server/proxy/pf_modules.c
@@ -0,0 +1,633 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server modules API
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <winpr/file.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <freerdp/api.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("modules")
+
+#define MODULE_ENTRY_POINT "proxy_module_entry_point"
+
+struct proxy_module
+{
+ proxyPluginsManager mgr;
+ wArrayList* plugins;
+ wArrayList* handles;
+};
+
+static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result)
+{
+ switch (result)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ return "FILTER_TYPE_KEYBOARD";
+ case FILTER_TYPE_UNICODE:
+ return "FILTER_TYPE_UNICODE";
+ case FILTER_TYPE_MOUSE:
+ return "FILTER_TYPE_MOUSE";
+ case FILTER_TYPE_MOUSE_EX:
+ return "FILTER_TYPE_MOUSE_EX";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE";
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR";
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ return "FILTER_TYPE_SERVER_PEER_LOGON";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE";
+ case FILTER_LAST:
+ return "FILTER_LAST";
+ default:
+ return "FILTER_UNKNOWN";
+ }
+}
+
+static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result)
+{
+ switch (result)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_INIT_CONNECT";
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_UNINIT_CONNECT";
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ return "HOOK_TYPE_CLIENT_PRE_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ return "HOOK_TYPE_CLIENT_POST_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ return "HOOK_TYPE_CLIENT_POST_DISCONNECT";
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ return "HOOK_TYPE_CLIENT_REDIRECT";
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ return "HOOK_TYPE_CLIENT_VERIFY_X509";
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ return "HOOK_TYPE_CLIENT_LOGIN_FAILURE";
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ return "HOOK_TYPE_CLIENT_END_PAINT";
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ return "HOOK_TYPE_SERVER_POST_CONNECT";
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ return "HOOK_TYPE_SERVER_ACTIVATE";
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ return "HOOK_TYPE_SERVER_CHANNELS_INIT";
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ return "HOOK_TYPE_SERVER_CHANNELS_FREE";
+ case HOOK_TYPE_SERVER_SESSION_END:
+ return "HOOK_TYPE_SERVER_SESSION_END";
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ return "HOOK_TYPE_CLIENT_LOAD_CHANNELS";
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ return "HOOK_TYPE_SERVER_SESSION_INITIALIZE";
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ return "HOOK_TYPE_SERVER_SESSION_STARTED";
+ case HOOK_LAST:
+ return "HOOK_LAST";
+ default:
+ return "HOOK_TYPE_UNKNOWN";
+ }
+}
+
+static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL ok = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* custom = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type));
+
+ switch (type)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_END:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom);
+ break;
+
+ case HOOK_LAST:
+ 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(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+ return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata,
+ custom);
+}
+
+static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* param = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running filter: %s", plugin->name);
+
+ switch (type)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_UNICODE:
+ result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE:
+ result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE_EX:
+ result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ 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");
+ }
+
+ 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(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ return ArrayList_ForEach(module->plugins, 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(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata, void* data)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+
+ WINPR_ASSERT(plugin_name);
+
+ ccharconv.ccp = plugin_name;
+ if (data == NULL) /* no need to store anything */
+ return FALSE;
+
+ if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
+ {
+ WLog_ERR(TAG, "[%s]: HashTable_Insert 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(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+ WINPR_ASSERT(plugin_name);
+ WINPR_ASSERT(pdata);
+ ccharconv.ccp = plugin_name;
+
+ return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp);
+}
+
+static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WLog_DBG(TAG, "is called!");
+ 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(proxyPluginsManager* mgr,
+ const proxyPlugin* plugin_to_register)
+{
+ proxyPlugin internal = { 0 };
+ proxyModule* module = (proxyModule*)mgr;
+ WINPR_ASSERT(module);
+
+ if (!plugin_to_register)
+ return FALSE;
+
+ internal = *plugin_to_register;
+ internal.mgr = mgr;
+
+ /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */
+ if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal))
+ return FALSE;
+
+ if (!ArrayList_Append(module->plugins, &internal))
+ {
+ WLog_ERR(TAG, "failed adding plugin to list: %s", 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*);
+ BOOL* res = va_arg(ap, BOOL*);
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+ WINPR_ASSERT(res);
+
+ if (strcmp(plugin->name, plugin_name) == 0)
+ *res = TRUE;
+ return TRUE;
+}
+
+BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(module);
+ if (ArrayList_Count(module->plugins) < 1)
+ return FALSE;
+ if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc))
+ return FALSE;
+ return rc;
+}
+
+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(proxyModule* module)
+{
+ size_t count = 0;
+
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ count = ArrayList_Count(module->plugins);
+
+ if (count > 0)
+ WLog_INFO(TAG, "Loaded plugins:");
+
+ ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt);
+}
+
+static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata)
+{
+ HMODULE handle = NULL;
+ proxyModuleEntryPoint pEntryPoint = NULL;
+ WINPR_ASSERT(module);
+
+ handle = LoadLibraryX(module_path);
+
+ if (handle == NULL)
+ {
+ WLog_ERR(TAG, "failed loading external library: %s", module_path);
+ return FALSE;
+ }
+
+ pEntryPoint = (proxyModuleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT);
+ if (!pEntryPoint)
+ {
+ WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path);
+ goto error;
+ }
+ if (!ArrayList_Append(module->handles, handle))
+ {
+ WLog_ERR(TAG, "ArrayList_Append failed!");
+ goto error;
+ }
+ return pf_modules_add(module, pEntryPoint, userdata);
+
+error:
+ FreeLibrary(handle);
+ return FALSE;
+}
+
+static void free_handle(void* obj)
+{
+ HANDLE handle = (HANDLE)obj;
+ if (handle)
+ FreeLibrary(handle);
+}
+
+static void free_plugin(void* obj)
+{
+ proxyPlugin* plugin = (proxyPlugin*)obj;
+ WINPR_ASSERT(plugin);
+
+ if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin))
+ WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name);
+
+ free(plugin);
+}
+
+static void* new_plugin(const void* obj)
+{
+ const proxyPlugin* src = obj;
+ proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
+ if (!proxy)
+ return NULL;
+ *proxy = *src;
+ return proxy;
+}
+
+proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
+{
+ wObject* obj = NULL;
+ char* path = NULL;
+ proxyModule* module = calloc(1, sizeof(proxyModule));
+ if (!module)
+ return NULL;
+
+ module->mgr.RegisterPlugin = pf_modules_register_plugin;
+ module->mgr.SetPluginData = pf_modules_set_plugin_data;
+ module->mgr.GetPluginData = pf_modules_get_plugin_data;
+ module->mgr.AbortConnect = pf_modules_abort_connect;
+ module->plugins = ArrayList_New(FALSE);
+
+ if (module->plugins == NULL)
+ {
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ obj = ArrayList_Object(module->plugins);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = free_plugin;
+ obj->fnObjectNew = new_plugin;
+
+ module->handles = ArrayList_New(FALSE);
+ if (module->handles == NULL)
+ {
+
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ ArrayList_Object(module->handles)->fnObjectFree = free_handle;
+
+ if (count > 0)
+ {
+ WINPR_ASSERT(root_dir);
+ if (!winpr_PathFileExists(root_dir))
+ path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir);
+ else
+ path = _strdup(root_dir);
+
+ if (!winpr_PathFileExists(path))
+ {
+ if (!winpr_PathMakePath(path, NULL))
+ {
+ WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
+ goto error;
+ }
+ }
+
+ if (winpr_PathFileExists(path))
+ WLog_DBG(TAG, "modules root directory: %s", path);
+
+ for (size_t i = 0; i < count; i++)
+ {
+ char name[8192] = { 0 };
+ char* fullpath = NULL;
+ _snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i],
+ FREERDP_SHARED_LIBRARY_SUFFIX);
+ fullpath = GetCombinedPath(path, name);
+ pf_modules_load_module(fullpath, module, NULL);
+ free(fullpath);
+ }
+ }
+
+ free(path);
+ return module;
+
+error:
+ free(path);
+ pf_modules_free(module);
+ return NULL;
+}
+
+void pf_modules_free(proxyModule* module)
+{
+ if (!module)
+ return;
+
+ ArrayList_Free(module->plugins);
+ ArrayList_Free(module->handles);
+ free(module);
+}
+
+BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(ep);
+
+ return ep(&module->mgr, userdata);
+}