diff options
Diffstat (limited to '')
-rw-r--r-- | channels/client/CMakeLists.txt | 125 | ||||
-rw-r--r-- | channels/client/addin.c | 761 | ||||
-rw-r--r-- | channels/client/addin.h | 28 | ||||
-rw-r--r-- | channels/client/generic_dynvc.c | 212 | ||||
-rw-r--r-- | channels/client/tables.c.in | 33 | ||||
-rw-r--r-- | channels/client/tables.h | 54 |
6 files changed, 1213 insertions, 0 deletions
diff --git a/channels/client/CMakeLists.txt b/channels/client/CMakeLists.txt new file mode 100644 index 0000000..923683a --- /dev/null +++ b/channels/client/CMakeLists.txt @@ -0,0 +1,125 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + +set(MODULE_NAME "freerdp-channels-client") +set(MODULE_PREFIX "FREERDP_CHANNELS_CLIENT") + +set(${MODULE_PREFIX}_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/tables.c + ${CMAKE_CURRENT_SOURCE_DIR}/tables.h + ${CMAKE_CURRENT_SOURCE_DIR}/addin.c + ${CMAKE_CURRENT_SOURCE_DIR}/addin.h + ${CMAKE_CURRENT_SOURCE_DIR}/generic_dynvc.c) + +if(CHANNEL_STATIC_CLIENT_ENTRIES) + list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES) +endif() + +set(CLIENT_STATIC_TYPEDEFS "#if __GNUC__\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic push\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic ignored \"-Wstrict-prototypes\"\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#endif\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}typedef UINT (*static_entry_fkt)();\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}typedef UINT (*static_addin_fkt)();\n") +set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#if __GNUC__\n") + set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#pragma GCC diagnostic pop\n") + set(CLIENT_STATIC_TYPEDEFS "${CLIENT_STATIC_TYPEDEFS}#endif\n") + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + if(${ENTRY} STREQUAL ${STATIC_ENTRY}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME}) + + set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${ENTRY}") + if(${ENTRY} STREQUAL "VirtualChannelEntry") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS);") + elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS,PVOID);") + elseif(${ENTRY} MATCHES "DVCPluginEntry$") + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(IDRDYNVC_ENTRY_POINTS* pEntryPoints);") + elseif(${ENTRY} MATCHES "DeviceServiceEntry$") + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);") + else() + set(ENTRY_POINT_IMPORT "extern UINT ${ENTRY_POINT_NAME}(void);") + endif() + set(${STATIC_ENTRY}_IMPORTS "${${STATIC_ENTRY}_IMPORTS}\n${ENTRY_POINT_IMPORT}") + set(${STATIC_ENTRY}_TABLE "${${STATIC_ENTRY}_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", (static_entry_fkt)${ENTRY_POINT_NAME} },") + endif() + endforeach() + endforeach() +endforeach() + +set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\nextern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[];\nconst STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[] =\n{") + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + set(CLIENT_STATIC_ENTRY_IMPORTS "${CLIENT_STATIC_ENTRY_IMPORTS}\n${${STATIC_ENTRY}_IMPORTS}") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\nextern const STATIC_ENTRY CLIENT_${STATIC_ENTRY}_TABLE[];\nconst STATIC_ENTRY CLIENT_${STATIC_ENTRY}_TABLE[] =\n{") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n${${STATIC_ENTRY}_TABLE}") + set(CLIENT_STATIC_ENTRY_TABLES "${CLIENT_STATIC_ENTRY_TABLES}\n\t{ NULL, NULL }\n};") + set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ \"${STATIC_ENTRY}\", CLIENT_${STATIC_ENTRY}_TABLE },") +endforeach() + +set(CLIENT_STATIC_ENTRY_TABLES_LIST "${CLIENT_STATIC_ENTRY_TABLES_LIST}\n\t{ NULL, NULL }\n};") + +set(CLIENT_STATIC_ADDIN_TABLE "extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[];\nconst STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[] =\n{") +foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + string(TOUPPER "CLIENT_${STATIC_MODULE_CHANNEL}_SUBSYSTEM_TABLE" SUBSYSTEM_TABLE_NAME) + set(SUBSYSTEM_TABLE "extern const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[];\nconst STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{") + get_target_property(CHANNEL_SUBSYSTEMS ${STATIC_MODULE_NAME} SUBSYSTEMS) + if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND") + set(CHANNEL_SUBSYSTEMS "") + endif() + foreach(STATIC_SUBSYSTEM ${CHANNEL_SUBSYSTEMS}) + if(${STATIC_SUBSYSTEM} MATCHES "^([^-]*)-(.*)") + string(REGEX REPLACE "^([^-]*)-(.*)" "\\1" STATIC_SUBSYSTEM_NAME ${STATIC_SUBSYSTEM}) + string(REGEX REPLACE "^([^-]*)-(.*)" "\\2" STATIC_SUBSYSTEM_TYPE ${STATIC_SUBSYSTEM}) + else() + set(STATIC_SUBSYSTEM_NAME "${STATIC_SUBSYSTEM}") + set(STATIC_SUBSYSTEM_TYPE "") + endif() + string(LENGTH "${STATIC_SUBSYSTEM_TYPE}" _type_length) + set(SUBSYSTEM_MODULE_NAME "${STATIC_MODULE_NAME}-${STATIC_SUBSYSTEM}") + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${SUBSYSTEM_MODULE_NAME}) + if(_type_length GREATER 0) + set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_${STATIC_SUBSYSTEM_TYPE}_subsystem_entry") + else() + set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_subsystem_entry") + endif() + set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} },") + set(SUBSYSTEM_IMPORT "extern UINT ${STATIC_SUBSYSTEM_ENTRY}(void*);") + set(CLIENT_STATIC_SUBSYSTEM_IMPORTS "${CLIENT_STATIC_SUBSYSTEM_IMPORTS}\n${SUBSYSTEM_IMPORT}") + endforeach() + set(SUBSYSTEM_TABLE "${SUBSYSTEM_TABLE}\n\t{ NULL, NULL, NULL }\n};") + set(CLIENT_STATIC_SUBSYSTEM_TABLES "${CLIENT_STATIC_SUBSYSTEM_TABLES}\n${SUBSYSTEM_TABLE}") + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + set (ENTRY_POINT_NAME ${STATIC_MODULE_CHANNEL}_${ENTRY}) + set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ \"${STATIC_MODULE_CHANNEL}\", \"${ENTRY}\", (static_addin_fkt)${ENTRY_POINT_NAME}, ${SUBSYSTEM_TABLE_NAME} },") + endforeach() +endforeach() +set(CLIENT_STATIC_ADDIN_TABLE "${CLIENT_STATIC_ADDIN_TABLE}\n\t{ NULL, NULL, NULL, NULL }\n};") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tables.c.in ${CMAKE_CURRENT_BINARY_DIR}/tables.c) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp winpr) + +set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) diff --git a/channels/client/addin.c b/channels/client/addin.c new file mode 100644 index 0000000..6d87f6c --- /dev/null +++ b/channels/client/addin.c @@ -0,0 +1,761 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/path.h> +#include <winpr/string.h> +#include <winpr/file.h> +#include <winpr/synch.h> +#include <winpr/library.h> +#include <winpr/collections.h> + +#include <freerdp/freerdp.h> +#include <freerdp/addin.h> +#include <freerdp/build-config.h> +#include <freerdp/client/channels.h> + +#include "tables.h" + +#include "addin.h" + +#include <freerdp/channels/log.h> +#define TAG CHANNELS_TAG("addin") + +extern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[]; + +static void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table, + const char* identifier) +{ + size_t index = 0; + const STATIC_ENTRY* pEntry = (const STATIC_ENTRY*)&table->table[index++]; + + while (pEntry->entry != NULL) + { + if (strcmp(pEntry->name, identifier) == 0) + { + return (void*)pEntry->entry; + } + + pEntry = (const STATIC_ENTRY*)&table->table[index++]; + } + + return NULL; +} + +void* freerdp_channels_client_find_static_entry(const char* name, const char* identifier) +{ + size_t index = 0; + const STATIC_ENTRY_TABLE* pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++]; + + while (pEntry->table != NULL) + { + if (strcmp(pEntry->name, name) == 0) + { + return freerdp_channels_find_static_entry_in_table(pEntry, identifier); + } + + pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++]; + } + + return NULL; +} + +extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[]; + +static FREERDP_ADDIN** freerdp_channels_list_client_static_addins(LPCSTR pszName, + LPCSTR pszSubsystem, + LPCSTR pszType, DWORD dwFlags) +{ + DWORD nAddins = 0; + FREERDP_ADDIN** ppAddins = NULL; + const STATIC_SUBSYSTEM_ENTRY* subsystems = NULL; + nAddins = 0; + ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + ppAddins[nAddins] = NULL; + + for (size_t i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != NULL; i++) + { + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", CLIENT_STATIC_ADDIN_TABLE[i].name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + subsystems = (const STATIC_SUBSYSTEM_ENTRY*)CLIENT_STATIC_ADDIN_TABLE[i].table; + + for (size_t j = 0; subsystems[j].name != NULL; j++) + { + pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", + CLIENT_STATIC_ADDIN_TABLE[i].name); + sprintf_s(pAddin->cSubsystem, ARRAYSIZE(pAddin->cSubsystem), "%s", subsystems[j].name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + } + } + + return ppAddins; +error_out: + freerdp_channels_addin_list_free(ppAddins); + return NULL; +} + +static HANDLE FindFirstFileUTF8(LPCSTR pszSearchPath, WIN32_FIND_DATAW* FindData) +{ + HANDLE hdl = INVALID_HANDLE_VALUE; + if (!pszSearchPath) + return hdl; + WCHAR* wpath = ConvertUtf8ToWCharAlloc(pszSearchPath, NULL); + if (!wpath) + return hdl; + + hdl = FindFirstFileW(wpath, FindData); + free(wpath); + + return hdl; +} + +static FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR pszType, DWORD dwFlags) +{ + int nDashes = 0; + HANDLE hFind = NULL; + DWORD nAddins = 0; + LPSTR pszPattern = NULL; + size_t cchPattern = 0; + LPCSTR pszAddinPath = FREERDP_ADDIN_PATH; + LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX; + LPCSTR pszExtension = NULL; + LPSTR pszSearchPath = NULL; + size_t cchSearchPath = 0; + size_t cchAddinPath = 0; + size_t cchInstallPrefix = 0; + FREERDP_ADDIN** ppAddins = NULL; + WIN32_FIND_DATAW FindData = { 0 }; + cchAddinPath = strnlen(pszAddinPath, sizeof(FREERDP_ADDIN_PATH)); + cchInstallPrefix = strnlen(pszInstallPrefix, sizeof(FREERDP_INSTALL_PREFIX)); + pszExtension = PathGetSharedLibraryExtensionA(0); + cchPattern = 128 + strnlen(pszExtension, MAX_PATH) + 2; + pszPattern = (LPSTR)malloc(cchPattern + 1); + + if (!pszPattern) + { + WLog_ERR(TAG, "malloc failed!"); + return NULL; + } + + if (pszName && pszSubsystem && pszType) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-%s-%s.%s", + pszName, pszSubsystem, pszType, pszExtension); + } + else if (pszName && pszType) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-?-%s.%s", + pszName, pszType, pszExtension); + } + else if (pszName) + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client*.%s", pszName, + pszExtension); + } + else + { + sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "?-client*.%s", + pszExtension); + } + + cchPattern = strnlen(pszPattern, cchPattern); + cchSearchPath = cchInstallPrefix + cchAddinPath + cchPattern + 3; + pszSearchPath = (LPSTR)calloc(cchSearchPath + 1, sizeof(char)); + + if (!pszSearchPath) + { + WLog_ERR(TAG, "malloc failed!"); + free(pszPattern); + return NULL; + } + + CopyMemory(pszSearchPath, pszInstallPrefix, cchInstallPrefix); + pszSearchPath[cchInstallPrefix] = '\0'; + const HRESULT hr1 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath); + const HRESULT hr2 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern); + free(pszPattern); + + if (FAILED(hr1) || FAILED(hr2)) + { + free(pszSearchPath); + return NULL; + } + + hFind = FindFirstFileUTF8(pszSearchPath, &FindData); + + free(pszSearchPath); + nAddins = 0; + ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + FindClose(hFind); + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + + if (hFind == INVALID_HANDLE_VALUE) + return ppAddins; + + do + { + char* cFileName = NULL; + BOOL used = FALSE; + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + cFileName = + ConvertWCharNToUtf8Alloc(FindData.cFileName, ARRAYSIZE(FindData.cFileName), NULL); + if (!cFileName) + goto skip; + + nDashes = 0; + for (size_t index = 0; cFileName[index]; index++) + nDashes += (cFileName[index] == '-') ? 1 : 0; + + if (nDashes == 1) + { + size_t len = 0; + char* p[2] = { 0 }; + /* <name>-client.<extension> */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 2) + { + size_t len = 0; + char* p[4] = { 0 }; + /* <name>-client-<subsystem>.<extension> */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + p[2] = strchr(p[1], '-'); + if (!p[2]) + goto skip; + p[2] += 1; + p[3] = strchr(p[2], '.'); + if (!p[3]) + goto skip; + p[3] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = (size_t)(p[3] - p[2]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 3) + { + size_t len = 0; + char* p[5] = { 0 }; + /* <name>-client-<subsystem>-<type>.<extension> */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + p[2] = strchr(p[1], '-'); + if (!p[2]) + goto skip; + p[2] += 1; + p[3] = strchr(p[2], '-'); + if (!p[3]) + goto skip; + p[3] += 1; + p[4] = strchr(p[3], '.'); + if (!p[4]) + goto skip; + p[4] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = (size_t)(p[3] - p[2]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + len = (size_t)(p[4] - p[3]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cType, p[3], MIN(ARRAYSIZE(pAddin->cType), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + pAddin->dwFlags |= FREERDP_ADDIN_TYPE; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + + skip: + free(cFileName); + if (!used) + free(pAddin); + + } while (FindNextFileW(hFind, &FindData)); + + FindClose(hFind); + ppAddins[nAddins] = NULL; + return ppAddins; +error_out: + FindClose(hFind); + freerdp_channels_addin_list_free(ppAddins); + return NULL; +} + +FREERDP_ADDIN** freerdp_channels_list_addins(LPCSTR pszName, LPCSTR pszSubsystem, LPCSTR pszType, + DWORD dwFlags) +{ + if (dwFlags & FREERDP_ADDIN_STATIC) + return freerdp_channels_list_client_static_addins(pszName, pszSubsystem, pszType, dwFlags); + else if (dwFlags & FREERDP_ADDIN_DYNAMIC) + return freerdp_channels_list_dynamic_addins(pszName, pszSubsystem, pszType, dwFlags); + + return NULL; +} + +void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins) +{ + if (!ppAddins) + return; + + for (size_t index = 0; ppAddins[index] != NULL; index++) + free(ppAddins[index]); + + free(ppAddins); +} + +extern const STATIC_ENTRY CLIENT_VirtualChannelEntryEx_TABLE[]; + +static BOOL freerdp_channels_is_virtual_channel_entry_ex(LPCSTR pszName) +{ + for (size_t i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != NULL; i++) + { + const STATIC_ENTRY* entry = &CLIENT_VirtualChannelEntryEx_TABLE[i]; + + if (!strncmp(entry->name, pszName, MAX_PATH)) + return TRUE; + } + + return FALSE; +} + +PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR pszType, DWORD dwFlags) +{ + const STATIC_ADDIN_TABLE* table = CLIENT_STATIC_ADDIN_TABLE; + const char* type = NULL; + + if (!pszName) + return NULL; + + if (dwFlags & FREERDP_ADDIN_CHANNEL_DYNAMIC) + type = "DVCPluginEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_DEVICE) + type = "DeviceServiceEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_STATIC) + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + type = "VirtualChannelEntryEx"; + else + type = "VirtualChannelEntry"; + } + + for (; table->name != NULL; table++) + { + if (strncmp(table->name, pszName, MAX_PATH) == 0) + { + if (type && strncmp(table->type, type, MAX_PATH)) + continue; + + if (pszSubsystem != NULL) + { + const STATIC_SUBSYSTEM_ENTRY* subsystems = table->table; + + for (; subsystems->name != NULL; subsystems++) + { + /* If the pszSubsystem is an empty string use the default backend. */ + if ((strnlen(pszSubsystem, 1) == + 0) || /* we only want to know if strnlen is > 0 */ + (strncmp(subsystems->name, pszSubsystem, MAX_PATH) == 0)) + { + if (pszType) + { + if (strncmp(subsystems->type, pszType, MAX_PATH) == 0) + return (PVIRTUALCHANNELENTRY)subsystems->entry; + } + else + { + return (PVIRTUALCHANNELENTRY)subsystems->entry; + } + } + } + } + else + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + { + if (!freerdp_channels_is_virtual_channel_entry_ex(pszName)) + return NULL; + } + + return (PVIRTUALCHANNELENTRY)table->entry; + } + } + } + + return NULL; +} + +typedef struct +{ + wMessageQueue* queue; + wStream* data_in; + HANDLE thread; + char* channel_name; + rdpContext* ctx; + LPVOID userdata; + MsgHandler msg_handler; +} msg_proc_internals; + +static DWORD WINAPI channel_client_thread_proc(LPVOID userdata) +{ + UINT error = CHANNEL_RC_OK; + wStream* data = NULL; + wMessage message = { 0 }; + msg_proc_internals* internals = userdata; + + WINPR_ASSERT(internals); + + while (1) + { + if (!MessageQueue_Wait(internals->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + if (!MessageQueue_Peek(internals->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*)message.wParam; + + if ((error = internals->msg_handler(internals->userdata, data))) + { + WLog_ERR(TAG, "msg_handler failed with error %" PRIu32 "!", error); + break; + } + } + } + if (error && internals->ctx) + { + char msg[128]; + _snprintf(msg, 127, + "%s_virtual_channel_client_thread reported an" + " error", + internals->channel_name); + setChannelError(internals->ctx, error, msg); + } + ExitThread(error); + return error; +} + +static void free_msg(void* obj) +{ + wMessage* msg = (wMessage*)obj; + + if (msg && (msg->id == 0)) + { + wStream* s = (wStream*)msg->wParam; + Stream_Free(s, TRUE); + } +} + +static void channel_client_handler_free(msg_proc_internals* internals) +{ + if (!internals) + return; + + if (internals->thread) + CloseHandle(internals->thread); + MessageQueue_Free(internals->queue); + Stream_Free(internals->data_in, TRUE); + free(internals->channel_name); + free(internals); +} + +/* Create message queue and thread or not, depending on settings */ +void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, MsgHandler msg_handler, + const char* channel_name) +{ + msg_proc_internals* internals = calloc(1, sizeof(msg_proc_internals)); + if (!internals) + { + WLog_ERR(TAG, "calloc failed!"); + return NULL; + } + internals->msg_handler = msg_handler; + internals->userdata = userdata; + if (channel_name) + { + internals->channel_name = _strdup(channel_name); + if (!internals->channel_name) + goto fail; + } + WINPR_ASSERT(ctx); + WINPR_ASSERT(ctx->settings); + internals->ctx = ctx; + if ((freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) == 0) + { + wObject obj = { 0 }; + obj.fnObjectFree = free_msg; + internals->queue = MessageQueue_New(&obj); + if (!internals->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto fail; + } + + if (!(internals->thread = + CreateThread(NULL, 0, channel_client_thread_proc, (void*)internals, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto fail; + } + } + return internals; + +fail: + channel_client_handler_free(internals); + return NULL; +} +/* post a message in the queue or directly call the processing handler */ +UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength, + UINT32 totalLength, UINT32 dataFlags) +{ + msg_proc_internals* internals = MsgsHandle; + wStream* data_in = NULL; + + if (!internals) + { + /* TODO: return some error here */ + return CHANNEL_RC_OK; + } + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (internals->data_in) + { + if (!Stream_EnsureCapacity(internals->data_in, totalLength)) + return CHANNEL_RC_NO_MEMORY; + } + else + internals->data_in = Stream_New(NULL, totalLength); + } + + if (!(data_in = internals->data_in)) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + Stream_Free(internals->data_in, TRUE); + internals->data_in = NULL; + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + char msg[128]; + _snprintf(msg, 127, "%s_plugin_process_received: read error", internals->channel_name); + WLog_ERR(TAG, msg); + return ERROR_INTERNAL_ERROR; + } + + internals->data_in = NULL; + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) != 0) + { + UINT error = CHANNEL_RC_OK; + if ((error = internals->msg_handler(internals->userdata, data_in))) + { + WLog_ERR(TAG, + "msg_handler failed with error" + " %" PRIu32 "!", + error); + return ERROR_INTERNAL_ERROR; + } + } + else if (!MessageQueue_Post(internals->queue, NULL, 0, (void*)data_in, NULL)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + return CHANNEL_RC_OK; +} +/* Tear down queue and thread */ +UINT channel_client_quit_handler(void* MsgsHandle) +{ + msg_proc_internals* internals = MsgsHandle; + UINT rc = 0; + if (!internals) + { + /* TODO: return some error here */ + return CHANNEL_RC_OK; + } + + WINPR_ASSERT(internals->ctx); + WINPR_ASSERT(internals->ctx->settings); + + if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) == 0) + { + if (internals->queue && internals->thread) + { + if (MessageQueue_PostQuit(internals->queue, 0) && + (WaitForSingleObject(internals->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc); + return rc; + } + } + } + + channel_client_handler_free(internals); + return CHANNEL_RC_OK; +} diff --git a/channels/client/addin.h b/channels/client/addin.h new file mode 100644 index 0000000..1b794e7 --- /dev/null +++ b/channels/client/addin.h @@ -0,0 +1,28 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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. + */ + +typedef UINT (*MsgHandler)(LPVOID userdata, wStream* data); + +FREERDP_API void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, + MsgHandler handler, const char* channel_name); + +UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength, + UINT32 totalLength, UINT32 dataFlags); + +UINT channel_client_quit_handler(void* MsgsHandle); diff --git a/channels/client/generic_dynvc.c b/channels/client/generic_dynvc.c new file mode 100644 index 0000000..263b5ce --- /dev/null +++ b/channels/client/generic_dynvc.c @@ -0,0 +1,212 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic channel + * + * Copyright 2022 David Fort <contact@hardening-consulting.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 <freerdp/config.h> +#include <freerdp/log.h> +#include <freerdp/client/channels.h> + +#define TAG FREERDP_TAG("genericdynvc") + +static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = NULL; + GENERIC_DYNVC_PLUGIN* plugin = NULL; + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + if (!listener_callback || !listener_callback->plugin) + return ERROR_INTERNAL_ERROR; + + plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin; + WLog_Print(plugin->log, WLOG_TRACE, "..."); + + callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize); + if (!callback) + { + WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* implant configured channel callbacks */ + callback->iface = *plugin->channel_callbacks; + + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + + listener_callback->channel_callback = callback; + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin, + IWTSVirtualChannelManager* pChannelMgr) +{ + UINT rc = 0; + GENERIC_LISTENER_CALLBACK* listener_callback = NULL; + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!pChannelMgr) + return ERROR_INVALID_PARAMETER; + + if (plugin->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name); + return ERROR_INVALID_DATA; + } + + WLog_Print(plugin->log, WLOG_TRACE, "..."); + listener_callback = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + if (!listener_callback) + { + WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + plugin->listener_callback = listener_callback; + listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection; + listener_callback->plugin = pPlugin; + listener_callback->channel_mgr = pChannelMgr; + rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface, + &plugin->listener); + + plugin->listener->pInterface = plugin->iface.pInterface; + plugin->initialized = (rc == CHANNEL_RC_OK); + return rc; +} + +static UINT generic_plugin_terminated(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(plugin->log, WLOG_TRACE, "..."); + + /* some channels (namely rdpei), look at initialized to see if they should continue to run */ + plugin->initialized = FALSE; + + if (plugin->terminatePluginFn) + plugin->terminatePluginFn(plugin); + + if (plugin->listener_callback) + { + IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, plugin->listener); + } + + free(plugin->listener_callback); + free(plugin->dynvc_name); + free(plugin); + return error; +} + +static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!pluginn) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + pluginn->attached = TRUE; + return error; +} + +static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + plugin->attached = FALSE; + return error; +} + +UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag, + const char* name, size_t pluginSize, size_t channelCallbackSize, + const IWTSVirtualChannelCallback* channel_callbacks, + DYNVC_PLUGIN_INIT_FN initPluginFn, + DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn) +{ + GENERIC_DYNVC_PLUGIN* plugin = NULL; + UINT error = CHANNEL_RC_INITIALIZATION_ERROR; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPlugin); + WINPR_ASSERT(logTag); + WINPR_ASSERT(name); + WINPR_ASSERT(pluginSize >= sizeof(*plugin)); + WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK)); + + plugin = (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name); + if (plugin != NULL) + return CHANNEL_RC_ALREADY_INITIALIZED; + + plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, pluginSize); + if (!plugin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + plugin->log = WLog_Get(logTag); + plugin->attached = TRUE; + plugin->channel_callbacks = channel_callbacks; + plugin->channelCallbackSize = channelCallbackSize; + plugin->iface.Initialize = generic_dynvc_plugin_initialize; + plugin->iface.Connected = NULL; + plugin->iface.Disconnected = NULL; + plugin->iface.Terminated = generic_plugin_terminated; + plugin->iface.Attached = generic_dynvc_plugin_attached; + plugin->iface.Detached = generic_dynvc_plugin_detached; + plugin->terminatePluginFn = terminatePluginFn; + + if (initPluginFn) + { + rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints); + rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints); + + error = initPluginFn(plugin, context, settings); + if (error != CHANNEL_RC_OK) + goto error; + } + + plugin->dynvc_name = _strdup(name); + if (!plugin->dynvc_name) + goto error; + + error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface); + if (error == CHANNEL_RC_OK) + return error; + +error: + generic_plugin_terminated(&plugin->iface); + return error; +} diff --git a/channels/client/tables.c.in b/channels/client/tables.c.in new file mode 100644 index 0000000..a22621b --- /dev/null +++ b/channels/client/tables.c.in @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 <freerdp/dvc.h> +#include <freerdp/channels/rdpdr.h> +#include "tables.h" + +${CLIENT_STATIC_TYPEDEFS} +${CLIENT_STATIC_ENTRY_IMPORTS} +${CLIENT_STATIC_ENTRY_TABLES} +${CLIENT_STATIC_ENTRY_TABLES_LIST} +${CLIENT_STATIC_SUBSYSTEM_IMPORTS} +${CLIENT_STATIC_SUBSYSTEM_TABLES} +${CLIENT_STATIC_ADDIN_TABLE} + diff --git a/channels/client/tables.h b/channels/client/tables.h new file mode 100644 index 0000000..e67beb5 --- /dev/null +++ b/channels/client/tables.h @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@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 <winpr/platform.h> +#include <freerdp/svc.h> + +/* The 'entry' function pointers have variable arguments. */ +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES + +typedef struct +{ + const char* name; + UINT (*entry)(); +} STATIC_ENTRY; + +typedef struct +{ + const char* name; + const STATIC_ENTRY* table; +} STATIC_ENTRY_TABLE; + +typedef struct +{ + const char* name; + const char* type; + UINT (*entry)(); +} STATIC_SUBSYSTEM_ENTRY; + +typedef struct +{ + const char* name; + const char* type; + UINT (*entry)(); + const STATIC_SUBSYSTEM_ENTRY* table; +} STATIC_ADDIN_TABLE; + +WINPR_PRAGMA_DIAG_POP |