diff --git a/include/freerdp/utils/helpers.h b/include/freerdp/utils/helpers.h index f0a2bbcff..bd2567852 100644 --- a/include/freerdp/utils/helpers.h +++ b/include/freerdp/utils/helpers.h @@ -60,6 +60,57 @@ extern "C" WINPR_ATTR_NODISCARD FREERDP_API WINPR_JSON* freerdp_GetJSONConfigFile(BOOL system, const char* filename); + /** @brief set \b vendor and \b product information for an application + * + * This sets the application details for an application instance. These values determine where + * to look for configuration files and other vendor/product specific settings data. + * This function recursively also sets \ref freerdp_setApplicationDetails with a 'vendor' string + * of 'vendor/product', a 'product' string of WINPR_PRODUCT_STRING (build time constant) and a + * 'version' of -1. This limits the length of \b vendor + \b product to \b MAX_PATH or less.. + * + * @note When calling this function, the compile time options \b + * FREERDP_USE_VENDOR_PRODUCT_CONFIG_DIR and \b WITH_FULL_CONFIG_PATH are ignored and the config + * path will always have the format 'vendor/product' or 'vendor/product1' (1 for the actual + * version set) + * + * @param vendor A vendor name to use. Must not be \b NULL. Must not contain forbidden + * filesystem symbols for any os. Must be less than \b MAX_PATH bytes. + * @param product A product name to use. Must not be \b NULL. Must not contain forbidden + * filesystem symbols for any os. Must be less than \b MAX_PATH bytes. + * @param version An optional versioning value to append to paths to settings. Use \b -1 to + * disable. + * + * @return \b TRUE if set successfully, \b FALSE in case of any error. + * @since version 3.23.0 + */ + FREERDP_API WINPR_ATTR_NODISCARD BOOL freerdp_setApplicationDetails(const char* vendor, + const char* product, + SSIZE_T version); + + /** @brief Get the current \b vendor string of the application. Defaults to \ref + * FREERDP_VENDOR_STRING + * + * @return The current string set as \b vendor. + * @since version 3.23.0 + */ + FREERDP_API WINPR_ATTR_NODISCARD const char* freerdp_getApplicationDetailsVendor(void); + + /** @brief Get the current \b product string of the application. Defaults to \ref + * FREERDP_PRODUCT_STRING + * + * @return The current string set as \b product. + * @since version 3.23.0 + */ + FREERDP_API WINPR_ATTR_NODISCARD const char* freerdp_getApplicationDetailsProduct(void); + + /** @brief Get the current \b version of the application. Defaults to \ref FREERDP_API_VERSION + * if \b WITH_RESOURCE_VERSIONING is defined, otherwise \b -1 + * + * @return The current number set as \b version + * @since version 3.23.0 + */ + FREERDP_API WINPR_ATTR_NODISCARD SSIZE_T freerdp_getApplicationDetailsVersion(void); + #ifdef __cplusplus } #endif diff --git a/libfreerdp/codec/rfx.c b/libfreerdp/codec/rfx.c index 3cf4217f8..ad45633fd 100644 --- a/libfreerdp/codec/rfx.c +++ b/libfreerdp/codec/rfx.c @@ -47,13 +47,14 @@ #include "rfx_quantization.h" #include "rfx_dwt.h" #include "rfx_rlgr.h" +#include "../core/utils.h" #include "sse/rfx_sse2.h" #include "neon/rfx_neon.h" #define TAG FREERDP_TAG("codec") -#define RFX_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\RemoteFX" +#define RFX_KEY "Software\\%s\\RemoteFX" /** * The quantization values control the compression rate and quality. The value @@ -259,30 +260,32 @@ RFX_CONTEXT* rfx_context_new_ex(BOOL encoder, UINT32 ThreadingFlags) if (!priv->BufferPool) goto fail; + priv->UseThreads = FALSE; if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS)) - { - HKEY hKey = NULL; priv->UseThreads = TRUE; - const LONG status = - RegOpenKeyExA(HKEY_LOCAL_MACHINE, RFX_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - - if (status == ERROR_SUCCESS) - { - DWORD dwType = 0; - DWORD dwValue = 0; - DWORD dwSize = sizeof(dwValue); - - if (RegQueryValueEx(hKey, _T("UseThreads"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == - ERROR_SUCCESS) - priv->UseThreads = dwValue ? 1 : 0; - - RegCloseKey(hKey); - } - } - else { - priv->UseThreads = FALSE; + char* key = freerdp_getApplicatonDetailsRegKey(RFX_KEY); + if (key) + { + HKEY hKey = NULL; + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, RFX_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); + + if (status == ERROR_SUCCESS) + { + DWORD dwType = 0; + DWORD dwValue = 0; + DWORD dwSize = sizeof(dwValue); + + if (RegQueryValueEx(hKey, _T("UseThreads"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + priv->UseThreads = dwValue ? 1 : 0; + + RegCloseKey(hKey); + } + } } if (priv->UseThreads) diff --git a/libfreerdp/core/credssp_auth.c b/libfreerdp/core/credssp_auth.c index e9afe92ad..0a488da46 100644 --- a/libfreerdp/core/credssp_auth.c +++ b/libfreerdp/core/credssp_auth.c @@ -40,7 +40,7 @@ #define TAG FREERDP_TAG("core.auth") -#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" +#define SERVER_KEY "Software\\%s\\Server" enum AUTH_STATE { @@ -788,8 +788,14 @@ static void auth_get_sspi_module_from_reg(char** sspi_module) WINPR_ASSERT(sspi_module); *sspi_module = NULL; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey) != - ERROR_SUCCESS) + char* key = freerdp_getApplicatonDetailsRegKey(SERVER_KEY); + if (!key) + return; + + const LONG rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); + + if (rc != ERROR_SUCCESS) return; if (RegQueryValueExA(hKey, "SspiModule", NULL, &dwType, NULL, &dwSize) != ERROR_SUCCESS) diff --git a/libfreerdp/core/nla.c b/libfreerdp/core/nla.c index 17d398aa0..a34bef682 100644 --- a/libfreerdp/core/nla.c +++ b/libfreerdp/core/nla.c @@ -54,8 +54,6 @@ #define TAG FREERDP_TAG("core.nla") -// #define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" - #define NLA_AUTH_PKG NEGO_SSP_NAME typedef enum diff --git a/libfreerdp/core/settings.c b/libfreerdp/core/settings.c index 5a3a65856..406110004 100644 --- a/libfreerdp/core/settings.c +++ b/libfreerdp/core/settings.c @@ -40,6 +40,7 @@ #include #include +#include "../core/utils.h" #include "../crypto/certificate.h" #include "../crypto/privatekey.h" #include "capabilities.h" @@ -53,8 +54,8 @@ static const char client_dll[] = "C:\\Windows\\System32\\mstscax.dll"; -#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" -#define CLIENT_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Client" +#define SERVER_KEY "Software\\%s\\Server" +#define CLIENT_KEY "Software\\%s\\Client" #define BITMAP_CACHE_KEY CLIENT_KEY "\\BitmapCacheV2" #define GLYPH_CACHE_KEY CLIENT_KEY "\\GlyphCache" #define POINTER_CACHE_KEY CLIENT_KEY "\\PointerCache" @@ -187,119 +188,165 @@ static BOOL settings_reg_query_bool(rdpSettings* settings, FreeRDP_Settings_Keys static void settings_client_load_hkey_local_machine(rdpSettings* settings) { - HKEY hKey = NULL; - LONG status = 0; - status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, CLIENT_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - - if (status == ERROR_SUCCESS) { - settings_reg_query_dword(settings, FreeRDP_DesktopWidth, hKey, _T("DesktopWidth")); - settings_reg_query_dword(settings, FreeRDP_DesktopHeight, hKey, _T("DesktopHeight")); - settings_reg_query_bool(settings, FreeRDP_Fullscreen, hKey, _T("Fullscreen")); - settings_reg_query_dword(settings, FreeRDP_ColorDepth, hKey, _T("ColorDepth")); - settings_reg_query_dword(settings, FreeRDP_KeyboardType, hKey, _T("KeyboardType")); - settings_reg_query_dword(settings, FreeRDP_KeyboardSubType, hKey, _T("KeyboardSubType")); - settings_reg_query_dword(settings, FreeRDP_KeyboardFunctionKey, hKey, - _T("KeyboardFunctionKeys")); - settings_reg_query_dword(settings, FreeRDP_KeyboardLayout, hKey, _T("KeyboardLayout")); - settings_reg_query_bool(settings, FreeRDP_ExtSecurity, hKey, _T("ExtSecurity")); - settings_reg_query_bool(settings, FreeRDP_NlaSecurity, hKey, _T("NlaSecurity")); - settings_reg_query_bool(settings, FreeRDP_TlsSecurity, hKey, _T("TlsSecurity")); - settings_reg_query_bool(settings, FreeRDP_RdpSecurity, hKey, _T("RdpSecurity")); - settings_reg_query_bool(settings, FreeRDP_MstscCookieMode, hKey, _T("MstscCookieMode")); - settings_reg_query_dword(settings, FreeRDP_CookieMaxLength, hKey, _T("CookieMaxLength")); - settings_reg_query_bool(settings, FreeRDP_BitmapCacheEnabled, hKey, _T("BitmapCache")); - settings_reg_query_dword(settings, FreeRDP_OffscreenSupportLevel, hKey, - _T("OffscreenBitmapCache")); - settings_reg_query_dword(settings, FreeRDP_OffscreenCacheSize, hKey, - _T("OffscreenBitmapCacheSize")); - settings_reg_query_dword(settings, FreeRDP_OffscreenCacheEntries, hKey, - _T("OffscreenBitmapCacheEntries")); - RegCloseKey(hKey); - } - - status = - RegOpenKeyExA(HKEY_LOCAL_MACHINE, BITMAP_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - - if (status == ERROR_SUCCESS) - { - settings_reg_query_dword(settings, FreeRDP_BitmapCacheV2NumCells, hKey, _T("NumCells")); - for (unsigned x = 0; x < 5; x++) + char* key = freerdp_getApplicatonDetailsRegKey(CLIENT_KEY); + if (key) { - DWORD val = 0; - TCHAR numentries[64] = { 0 }; - TCHAR persist[64] = { 0 }; - BITMAP_CACHE_V2_CELL_INFO cache = { 0 }; - (void)_sntprintf(numentries, ARRAYSIZE(numentries), _T("Cell%uNumEntries"), x); - (void)_sntprintf(persist, ARRAYSIZE(persist), _T("Cell%uPersistent"), x); - if (!settings_reg_query_dword_val(hKey, numentries, &val) || - !settings_reg_query_bool_val(hKey, persist, &cache.persistent) || - !freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, x, - &cache)) - WLog_WARN(TAG, "Failed to load registry keys to settings!"); - cache.numEntries = val; - } + HKEY hKey = NULL; + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); - settings_reg_query_bool(settings, FreeRDP_AllowCacheWaitingList, hKey, - _T("AllowCacheWaitingList")); - RegCloseKey(hKey); + if (status == ERROR_SUCCESS) + { + settings_reg_query_dword(settings, FreeRDP_DesktopWidth, hKey, _T("DesktopWidth")); + settings_reg_query_dword(settings, FreeRDP_DesktopHeight, hKey, + _T("DesktopHeight")); + settings_reg_query_bool(settings, FreeRDP_Fullscreen, hKey, _T("Fullscreen")); + settings_reg_query_dword(settings, FreeRDP_ColorDepth, hKey, _T("ColorDepth")); + settings_reg_query_dword(settings, FreeRDP_KeyboardType, hKey, _T("KeyboardType")); + settings_reg_query_dword(settings, FreeRDP_KeyboardSubType, hKey, + _T("KeyboardSubType")); + settings_reg_query_dword(settings, FreeRDP_KeyboardFunctionKey, hKey, + _T("KeyboardFunctionKeys")); + settings_reg_query_dword(settings, FreeRDP_KeyboardLayout, hKey, + _T("KeyboardLayout")); + settings_reg_query_bool(settings, FreeRDP_ExtSecurity, hKey, _T("ExtSecurity")); + settings_reg_query_bool(settings, FreeRDP_NlaSecurity, hKey, _T("NlaSecurity")); + settings_reg_query_bool(settings, FreeRDP_TlsSecurity, hKey, _T("TlsSecurity")); + settings_reg_query_bool(settings, FreeRDP_RdpSecurity, hKey, _T("RdpSecurity")); + settings_reg_query_bool(settings, FreeRDP_MstscCookieMode, hKey, + _T("MstscCookieMode")); + settings_reg_query_dword(settings, FreeRDP_CookieMaxLength, hKey, + _T("CookieMaxLength")); + settings_reg_query_bool(settings, FreeRDP_BitmapCacheEnabled, hKey, + _T("BitmapCache")); + settings_reg_query_dword(settings, FreeRDP_OffscreenSupportLevel, hKey, + _T("OffscreenBitmapCache")); + settings_reg_query_dword(settings, FreeRDP_OffscreenCacheSize, hKey, + _T("OffscreenBitmapCacheSize")); + settings_reg_query_dword(settings, FreeRDP_OffscreenCacheEntries, hKey, + _T("OffscreenBitmapCacheEntries")); + RegCloseKey(hKey); + } + } } - - status = - RegOpenKeyExA(HKEY_LOCAL_MACHINE, GLYPH_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - - if (status == ERROR_SUCCESS) { - unsigned x = 0; - UINT32 GlyphSupportLevel = 0; - settings_reg_query_dword(settings, FreeRDP_GlyphSupportLevel, hKey, _T("SupportLevel")); - for (; x < 10; x++) + char* key = freerdp_getApplicatonDetailsRegKey(BITMAP_CACHE_KEY); + if (key) { - GLYPH_CACHE_DEFINITION cache = { 0 }; - TCHAR numentries[64] = { 0 }; - TCHAR maxsize[64] = { 0 }; - (void)_sntprintf(numentries, ARRAYSIZE(numentries), _T("Cache%uNumEntries"), x); - (void)_sntprintf(maxsize, ARRAYSIZE(maxsize), _T("Cache%uMaxCellSize"), x); + HKEY hKey = NULL; + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); - settings_reg_query_word_val(hKey, numentries, &cache.cacheEntries); - settings_reg_query_word_val(hKey, maxsize, &cache.cacheMaximumCellSize); - if (!freerdp_settings_set_pointer_array(settings, FreeRDP_GlyphCache, x, &cache)) - WLog_WARN(TAG, "Failed to store GlyphCache %u", x); + if (status == ERROR_SUCCESS) + { + settings_reg_query_dword(settings, FreeRDP_BitmapCacheV2NumCells, hKey, + _T("NumCells")); + for (unsigned x = 0; x < 5; x++) + { + DWORD val = 0; + TCHAR numentries[64] = { 0 }; + TCHAR persist[64] = { 0 }; + BITMAP_CACHE_V2_CELL_INFO cache = { 0 }; + (void)_sntprintf(numentries, ARRAYSIZE(numentries), _T("Cell%uNumEntries"), x); + (void)_sntprintf(persist, ARRAYSIZE(persist), _T("Cell%uPersistent"), x); + if (!settings_reg_query_dword_val(hKey, numentries, &val) || + !settings_reg_query_bool_val(hKey, persist, &cache.persistent) || + !freerdp_settings_set_pointer_array(settings, FreeRDP_BitmapCacheV2CellInfo, + x, &cache)) + WLog_WARN(TAG, "Failed to load registry keys to settings!"); + cache.numEntries = val; + } + + settings_reg_query_bool(settings, FreeRDP_AllowCacheWaitingList, hKey, + _T("AllowCacheWaitingList")); + RegCloseKey(hKey); + } } - { - GLYPH_CACHE_DEFINITION cache = { 0 }; - settings_reg_query_word_val(hKey, _T("FragCacheNumEntries"), &cache.cacheEntries); - settings_reg_query_word_val(hKey, _T("FragCacheMaxCellSize"), - &cache.cacheMaximumCellSize); - if (!freerdp_settings_set_pointer_array(settings, FreeRDP_FragCache, x, &cache)) - WLog_WARN(TAG, "Failed to store FragCache"); - } - - RegCloseKey(hKey); - - if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GlyphSupportLevel)) - WLog_WARN(TAG, "Failed to load registry keys to settings!"); } - - status = - RegOpenKeyExA(HKEY_LOCAL_MACHINE, POINTER_CACHE_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - - if (status == ERROR_SUCCESS) { - settings_reg_query_dword(settings, FreeRDP_LargePointerFlag, hKey, _T("LargePointer")); - settings_reg_query_dword(settings, FreeRDP_PointerCacheSize, hKey, _T("PointerCacheSize")); - settings_reg_query_dword(settings, FreeRDP_ColorPointerCacheSize, hKey, - _T("ColorPointerCacheSize")); - RegCloseKey(hKey); + char* key = freerdp_getApplicatonDetailsRegKey(GLYPH_CACHE_KEY); + if (key) + { + HKEY hKey = NULL; + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); + + if (status == ERROR_SUCCESS) + { + unsigned x = 0; + UINT32 GlyphSupportLevel = 0; + settings_reg_query_dword(settings, FreeRDP_GlyphSupportLevel, hKey, + _T("SupportLevel")); + for (; x < 10; x++) + { + GLYPH_CACHE_DEFINITION cache = { 0 }; + TCHAR numentries[64] = { 0 }; + TCHAR maxsize[64] = { 0 }; + (void)_sntprintf(numentries, ARRAYSIZE(numentries), _T("Cache%uNumEntries"), x); + (void)_sntprintf(maxsize, ARRAYSIZE(maxsize), _T("Cache%uMaxCellSize"), x); + + settings_reg_query_word_val(hKey, numentries, &cache.cacheEntries); + settings_reg_query_word_val(hKey, maxsize, &cache.cacheMaximumCellSize); + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_GlyphCache, x, + &cache)) + WLog_WARN(TAG, "Failed to store GlyphCache %u", x); + } + { + GLYPH_CACHE_DEFINITION cache = { 0 }; + settings_reg_query_word_val(hKey, _T("FragCacheNumEntries"), + &cache.cacheEntries); + settings_reg_query_word_val(hKey, _T("FragCacheMaxCellSize"), + &cache.cacheMaximumCellSize); + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_FragCache, x, &cache)) + WLog_WARN(TAG, "Failed to store FragCache"); + } + + RegCloseKey(hKey); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, + GlyphSupportLevel)) + WLog_WARN(TAG, "Failed to load registry keys to settings!"); + } + } + } + { + char* key = freerdp_getApplicatonDetailsRegKey(POINTER_CACHE_KEY); + if (key) + { + HKEY hKey = NULL; + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); + + if (status == ERROR_SUCCESS) + { + settings_reg_query_dword(settings, FreeRDP_LargePointerFlag, hKey, + _T("LargePointer")); + settings_reg_query_dword(settings, FreeRDP_PointerCacheSize, hKey, + _T("PointerCacheSize")); + settings_reg_query_dword(settings, FreeRDP_ColorPointerCacheSize, hKey, + _T("ColorPointerCacheSize")); + RegCloseKey(hKey); + } + } } } static void settings_server_load_hkey_local_machine(rdpSettings* settings) { HKEY hKey = NULL; - LONG status = 0; - status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + char* key = freerdp_getApplicatonDetailsRegKey(SERVER_KEY); + if (!key) + return; + + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); if (status != ERROR_SUCCESS) return; diff --git a/libfreerdp/core/test/CMakeLists.txt b/libfreerdp/core/test/CMakeLists.txt index 736d45cab..208070dab 100644 --- a/libfreerdp/core/test/CMakeLists.txt +++ b/libfreerdp/core/test/CMakeLists.txt @@ -3,9 +3,11 @@ set(MODULE_PREFIX "TEST_CORE") disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + set(DRIVER ${MODULE_NAME}.c) -set(TESTS TestVersion.c TestSettings.c) +set(TESTS TestVersion.c TestSettings.c TestUtils.c) if(BUILD_TESTING_INTERNAL) list(APPEND TESTS TestStreamDump.c) diff --git a/libfreerdp/core/test/TestUtils.c b/libfreerdp/core/test/TestUtils.c new file mode 100644 index 000000000..1c2f78d43 --- /dev/null +++ b/libfreerdp/core/test/TestUtils.c @@ -0,0 +1,434 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(BUILD_TESTING_INTERNAL) +#include "../utils.h" +#endif + +typedef struct +{ + const char* vendor; + const char* product; + SSIZE_T version; +} test_case_t; + +static const test_case_t tests[] = { { "foobar", "gaga", 23 }, + { "foobar1", "gaga1", -1 }, + { "foobar2", "gaga2", 23 }, + { "foobar3", "gaga3", -1 } }; + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* create(const char* vendor, const char* product, SSIZE_T version, char separator) +{ + char* wvendor = NULL; + size_t wlen = 0; + if (version < 0) + (void)winpr_asprintf(&wvendor, &wlen, "%s%c%s", vendor, separator, product); + else + (void)winpr_asprintf(&wvendor, &wlen, "%s%c%s%" PRIdz, vendor, separator, product, version); + return wvendor; +} + +static bool checkCombined(const char* what, const char* vendor, const char* product, + SSIZE_T version, char separator) +{ + if (!what || !vendor || !product) + return false; + + char* cmp = create(vendor, product, version, separator); + if (!cmp) + return false; + + const bool rc = strcmp(what, cmp) == 0; + free(cmp); + return rc; +} + +#if !defined(FREERDP_USE_VENDOR_PRODUCT_CONFIG_DIR) +#if !defined(WITH_FULL_CONFIG_PATH) +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* freerdp_settings_get_legacy_config_path(const char* filename, const char* cproduct) +{ + char product[MAX_PATH] = { 0 }; + + const size_t plen = strnlen(cproduct, sizeof(product)); + if (plen == MAX_PATH) + return NULL; + + for (size_t i = 0; i < plen; i++) + product[i] = (char)tolower(cproduct[i]); + + char* path = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, product); + + if (!path) + return NULL; + + if (!filename) + return path; + + char* filepath = GetCombinedPath(path, filename); + free(path); + return filepath; +} +#endif + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +char* getFreeRDPDefaultConfig(BOOL system, const char* product, const char* vendor, SSIZE_T version, + const char* filename) +{ + eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; + + if (!vendor || !product) + return NULL; + +#if !defined(WITH_FULL_CONFIG_PATH) + if (!system && (_stricmp(vendor, product) == 0)) + return freerdp_settings_get_legacy_config_path(filename, product); +#endif + + char* config = GetKnownPath(id); + if (!config) + return NULL; + + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); + + if (!base) + return NULL; + + if (!filename) + return base; + + char* path = GetCombinedPathV(base, "%s", filename); + free(base); + return path; +} +#endif + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* getFreeRDPConfig(bool custom, BOOL system, const char* vendor, const char* product, + SSIZE_T version, const char* filename) +{ +#if !defined(FREERDP_USE_VENDOR_PRODUCT_CONFIG_DIR) + if (!custom) + return getFreeRDPDefaultConfig(system, vendor, product, version, filename); +#endif + eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; + char* config = GetKnownSubPathV(id, "%s", vendor); + if (!config) + return NULL; + + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); + + if (!base) + return NULL; + if (!filename) + return base; + + char* path = GetCombinedPath(base, filename); + free(base); + return path; +} + +WINPR_ATTR_NODISCARD +static bool checkFreeRDPConfig(bool custom, const char* what, BOOL system, const char* vendor, + const char* product, SSIZE_T version, const char* filename) +{ + if (!what) + return false; + char* cmp = getFreeRDPConfig(custom, system, vendor, product, version, filename); + if (!cmp) + return false; + + const bool rc = strcmp(what, cmp) == 0; + free(cmp); + return rc; +} + +static bool checkFreeRDPResults(bool custom, const char* vendor, const char* product, + SSIZE_T version) +{ + const char* cvendor = freerdp_getApplicationDetailsVendor(); + const char* cproduct = freerdp_getApplicationDetailsProduct(); + const SSIZE_T cversion = freerdp_getApplicationDetailsVersion(); + + if (!custom) + { +#if !defined(WITH_RESOURCE_VERSIONING) + version = -1; +#endif + } + + if (strcmp(cvendor, vendor) != 0) + { + (void)fprintf(stderr, "freerdp_getApplicationDetailsVendor returned '%s', expected '%s'\n", + cvendor, vendor); + return false; + } + if (strcmp(cvendor, vendor) != 0) + { + (void)fprintf(stderr, "freerdp_getApplicationDetailsProduct returned '%s', expected '%s'\n", + cproduct, product); + return false; + } + if (cversion != version) + { + (void)fprintf(stderr, + "freerdp_getApplicationDetailsVersion returned %" PRIdz ", expected %" PRIdz + "\n", + cversion, version); + return false; + } + + { + char* sys = freerdp_GetConfigFilePath(TRUE, NULL); + const bool rc = checkFreeRDPConfig(custom, sys, TRUE, vendor, product, version, NULL); + free(sys); + if (!rc) + return rc; + } + { + const char name[] = "systest"; + char* sys = freerdp_GetConfigFilePath(TRUE, name); + const bool rc = checkFreeRDPConfig(custom, sys, TRUE, vendor, product, version, name); + free(sys); + if (!rc) + return rc; + } + { + char* sys = freerdp_GetConfigFilePath(FALSE, NULL); + const bool rc = checkFreeRDPConfig(custom, sys, FALSE, vendor, product, version, NULL); + free(sys); + if (!rc) + return rc; + } + { + const char name[] = "usertest"; + char* sys = freerdp_GetConfigFilePath(FALSE, name); + const bool rc = checkFreeRDPConfig(custom, sys, FALSE, vendor, product, version, name); + free(sys); + if (!rc) + return rc; + } + +#if defined(BUILD_TESTING_INTERNAL) + { + char* pcmp = create(vendor, product, version, '\\'); + if (!pcmp) + return false; + char* cmp = NULL; + size_t clen = 0; +#define FMT "foo\\bar\\%s\\gaga" + (void)winpr_asprintf(&cmp, &clen, FMT, pcmp); + free(pcmp); + if (!cmp) + return false; + + char* comb = freerdp_getApplicatonDetailsRegKey(FMT); +#undef FMT + + bool rc = false; + if (comb) + { + rc = strcmp(cmp, comb) == 0; + } + free(comb); + free(cmp); + + if (!rc) + return false; + } + { + char* comb = freerdp_getApplicatonDetailsCombined('/'); + const bool rc = checkCombined(comb, vendor, product, version, '/'); + free(comb); + if (!rc) + return false; + } + { + char* comb = freerdp_getApplicatonDetailsCombined('\\'); + const bool rc = checkCombined(comb, vendor, product, version, '\\'); + free(comb); + if (!rc) + return false; + } + const BOOL ccustom = freerdp_areApplicationDetailsCustomized(); + if (ccustom != custom) + { + (void)fprintf(stderr, "freerdp_areApplicationDetailsCustomized returned %d, expected%d\n", + ccustom, custom); + return false; + } +#endif + + return true; +} + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* getWinPRConfig(BOOL system, const char* vendor, const char* product, SSIZE_T version, + const char* filename) +{ + eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; + char* config = GetKnownSubPathV(id, "%s", vendor); + if (!config) + return NULL; + + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); + + if (!base) + return NULL; + if (!filename) + return base; + + char* path = GetCombinedPath(base, filename); + free(base); + return path; +} + +WINPR_ATTR_NODISCARD +static bool checkWinPRConfig(const char* what, BOOL system, const char* vendor, const char* product, + SSIZE_T version, const char* filename) +{ + if (!what) + return false; + char* cmp = getWinPRConfig(system, vendor, product, version, filename); + if (!cmp) + return false; + + const bool rc = strcmp(what, cmp) == 0; + free(cmp); + return rc; +} + +WINPR_ATTR_NODISCARD +static bool checkWinPRResults(bool custom, const char* vendor, const char* product, SSIZE_T version) +{ + const char* cvendor = winpr_getApplicationDetailsVendor(); + const char* cproduct = winpr_getApplicationDetailsProduct(); + const SSIZE_T cversion = winpr_getApplicationDetailsVersion(); + + if (!custom) + { +#if !defined(WITH_RESOURCE_VERSIONING) + version = -1; +#endif + } + + if (strcmp(cvendor, vendor) != 0) + { + (void)fprintf(stderr, "winpr_getApplicationDetailsVendor returned '%s', expected '%s'\n", + cvendor, vendor); + return false; + } + if (strcmp(cvendor, vendor) != 0) + { + (void)fprintf(stderr, "winpr_getApplicationDetailsProduct returned '%s', expected '%s'\n", + cproduct, product); + return false; + } + if (cversion != version) + { + (void)fprintf( + stderr, "winpr_getApplicationDetailsVersion returned %" PRIdz ", expected %" PRIdz "\n", + cversion, version); + return false; + } + + { + char* sys = winpr_GetConfigFilePath(TRUE, NULL); + const bool rc = checkWinPRConfig(sys, TRUE, vendor, product, version, NULL); + free(sys); + if (!rc) + return rc; + } + { + char* sys = winpr_GetConfigFilePath(TRUE, "systest"); + const bool rc = checkWinPRConfig(sys, TRUE, vendor, product, version, "systest"); + free(sys); + if (!rc) + return rc; + } + { + char* sys = winpr_GetConfigFilePath(FALSE, NULL); + const bool rc = checkWinPRConfig(sys, FALSE, vendor, product, version, NULL); + free(sys); + if (!rc) + return rc; + } + { + char* sys = winpr_GetConfigFilePath(FALSE, "usertest"); + const bool rc = checkWinPRConfig(sys, FALSE, vendor, product, version, "usertest"); + free(sys); + if (!rc) + return rc; + } + + return true; +} + +int TestUtils(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char* argv[]) +{ + if (!checkWinPRResults(false, WINPR_VENDOR_STRING, WINPR_PRODUCT_STRING, WINPR_VERSION_MAJOR)) + return -1; + if (!checkFreeRDPResults(false, FREERDP_VENDOR_STRING, FREERDP_PRODUCT_STRING, + FREERDP_VERSION_MAJOR)) + return -2; + + for (size_t x = 0; x < ARRAYSIZE(tests); x++) + { + const test_case_t* cur = &tests[x]; + + if (!freerdp_setApplicationDetails(cur->vendor, cur->product, cur->version)) + { + (void)fprintf(stderr, "freerdp_setApplicationDetails(%s, %s, %" PRIdz ") failed\n", + cur->vendor, cur->product, cur->version); + return -3; + } + + const char separator = PathGetSeparatorA(PATH_STYLE_NATIVE); +#if defined(BUILD_TESTING_INTERNAL) + char* wvendor = freerdp_getApplicatonDetailsCombined(separator); +#else + char* wvendor = create(cur->vendor, cur->product, cur->version, separator); +#endif + if (!wvendor) + return -4; + const BOOL wrc = checkWinPRResults(true, wvendor, "WinPR", -1); + free(wvendor); + if (!wrc) + return -5; + if (!checkFreeRDPResults(true, cur->vendor, cur->product, cur->version)) + return -6; + } + + printf("%s: success\n", __func__); + return 0; +} diff --git a/libfreerdp/core/utils.h b/libfreerdp/core/utils.h index 252a6244c..6d750de4d 100644 --- a/libfreerdp/core/utils.h +++ b/libfreerdp/core/utils.h @@ -62,4 +62,35 @@ char* utils_redir_flags_to_string(UINT32 flags, char* buffer, size_t size); BOOL utils_reload_channels(rdpContext* context); +/** @brief generate a registry key string of format 'someting\\%s\\foo' + * + * @param fmt A format string that must contain a single '%s' being replaced by the + * 'vendor\\product` values. + * + * @return A registry key to use or \b NULL if failed. + * @version since 3.23.0 + */ +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +char* freerdp_getApplicatonDetailsRegKey(WINPR_FORMAT_ARG const char* fmt); + +/** @brief generate a 'vendor/product' string with desired separator + * + * @param separator the separator character to use + * + * @return A 'vendor/product' string to use or \b NULL if failed. + * @version since 3.23.0 + */ +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +char* freerdp_getApplicatonDetailsCombined(char separator); + +/** @brief returns if we are using default compile time 'vendor' and 'product' settings or an + * application provided one. + * + * @return \b TRUE if \b freerdp_setApplicationDetails was called, \b FALSE otherwise. + * @version since 3.23.0 + */ +WINPR_ATTR_NODISCARD BOOL freerdp_areApplicationDetailsCustomized(void); + #endif /* FREERDP_LIB_CORE_UTILS_H */ diff --git a/libfreerdp/utils/helpers.c b/libfreerdp/utils/helpers.c index 85faa747b..ff1cc3703 100644 --- a/libfreerdp/utils/helpers.c +++ b/libfreerdp/utils/helpers.c @@ -23,9 +23,134 @@ #include #include +#include +#include #include #include +#include "../core/utils.h" + +static INIT_ONCE s_freerdp_app_details_once = INIT_ONCE_STATIC_INIT; +static char s_freerdp_vendor_string[MAX_PATH] = { 0 }; +static char s_freerdp_product_string[MAX_PATH] = { 0 }; +static SSIZE_T s_freerdp_version = -1; +static BOOL s_freerdp_app_details_are_custom = FALSE; + +static BOOL CALLBACK init_app_details(WINPR_ATTR_UNUSED PINIT_ONCE once, + WINPR_ATTR_UNUSED PVOID param, + WINPR_ATTR_UNUSED PVOID* context) +{ + const size_t vlen = sizeof(FREERDP_VENDOR_STRING); + const size_t plen = sizeof(FREERDP_PRODUCT_STRING); + const char* rvlen = strncpy(s_freerdp_vendor_string, FREERDP_VENDOR_STRING, vlen); + const char* rplen = strncpy(s_freerdp_product_string, FREERDP_PRODUCT_STRING, plen); + if (!rvlen || !rplen) + return FALSE; + +#if defined(WITH_RESOURCE_VERSIONING) + s_freerdp_version = FREERDP_API_VERSION; +#else + s_freerdp_version = -1; +#endif + return TRUE; +} + +static WINPR_ATTR_NODISCARD BOOL initializeApplicationDetails(void) +{ + InitOnceExecuteOnce(&s_freerdp_app_details_once, init_app_details, NULL, NULL); + return TRUE; +} + +BOOL freerdp_setApplicationDetails(const char* vendor, const char* product, SSIZE_T version) +{ + if (!initializeApplicationDetails()) + return -1; + + if (!vendor || !product) + return FALSE; + const size_t vlen = strnlen(vendor, MAX_PATH); + const size_t plen = strnlen(product, MAX_PATH); + if ((vlen == MAX_PATH) || (plen == MAX_PATH)) + return FALSE; + + if (!strncpy(s_freerdp_vendor_string, vendor, vlen + 1)) + return FALSE; + + if (!strncpy(s_freerdp_product_string, product, plen + 1)) + return FALSE; + + s_freerdp_version = version; + s_freerdp_app_details_are_custom = TRUE; + + const char separator = PathGetSeparatorA(PATH_STYLE_NATIVE); + char* str = freerdp_getApplicatonDetailsCombined(separator); + if (!str) + return FALSE; + + const BOOL rc = winpr_setApplicationDetails(str, "WinPR", -1); + free(str); + return rc; +} + +const char* freerdp_getApplicationDetailsVendor(void) +{ + if (!initializeApplicationDetails()) + return NULL; + return s_freerdp_vendor_string; +} + +const char* freerdp_getApplicationDetailsProduct(void) +{ + if (!initializeApplicationDetails()) + return NULL; + return s_freerdp_product_string; +} + +char* freerdp_getApplicatonDetailsRegKey(const char* fmt) +{ + char* val = freerdp_getApplicatonDetailsCombined('\\'); + if (!val) + return NULL; + + char* str = NULL; + size_t slen = 0; + (void)winpr_asprintf(&str, &slen, fmt, val); + free(val); + return str; +} + +char* freerdp_getApplicatonDetailsCombined(char separator) +{ + const SSIZE_T version = freerdp_getApplicationDetailsVersion(); + const char* vendor = freerdp_getApplicationDetailsVendor(); + const char* product = freerdp_getApplicationDetailsProduct(); + + size_t slen = 0; + char* str = NULL; + if (version < 0) + { + (void)winpr_asprintf(&str, &slen, "%s%c%s", vendor, separator, product); + } + else + { + (void)winpr_asprintf(&str, &slen, "%s%c%s%" PRIdz, vendor, separator, product, version); + } + + return str; +} + +SSIZE_T freerdp_getApplicationDetailsVersion(void) +{ + if (!initializeApplicationDetails()) + return -1; + return s_freerdp_version; +} + +BOOL freerdp_areApplicationDetailsCustomized(void) +{ + return s_freerdp_app_details_are_custom; +} + #if !defined(WITH_FULL_CONFIG_PATH) WINPR_ATTR_MALLOC(free, 1) WINPR_ATTR_NODISCARD @@ -47,31 +172,28 @@ static char* freerdp_settings_get_legacy_config_path(const char* filename) } #endif -char* freerdp_GetConfigFilePath(BOOL system, const char* filename) +WINPR_ATTR_NODISCARD +WINPR_ATTR_MALLOC(free, 1) static char* getCustomConfigPath(BOOL system, const char* filename) { eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; -#if defined(FREERDP_USE_VENDOR_PRODUCT_CONFIG_DIR) - char* vendor = GetKnownSubPath(id, FREERDP_VENDOR_STRING); -#else -#if !defined(WITH_FULL_CONFIG_PATH) - if (!system && (_stricmp(FREERDP_VENDOR_STRING, FREERDP_PRODUCT_STRING) == 0)) - return freerdp_settings_get_legacy_config_path(filename); -#endif + const char* vendor = freerdp_getApplicationDetailsVendor(); + const char* product = freerdp_getApplicationDetailsProduct(); + const SSIZE_T version = freerdp_getApplicationDetailsVersion(); - char* vendor = GetKnownPath(id); -#endif - if (!vendor) + if (!vendor || !product) return NULL; -#if defined(WITH_RESOURCE_VERSIONING) - const char* verstr = FREERDP_PRODUCT_STRING FREERDP_API_VERSION; -#else - const char* verstr = FREERDP_PRODUCT_STRING; -#endif + char* config = GetKnownSubPathV(id, "%s", vendor); + if (!config) + return NULL; - char* base = GetCombinedPath(vendor, verstr); - free(vendor); + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); if (!base) return NULL; @@ -79,7 +201,53 @@ char* freerdp_GetConfigFilePath(BOOL system, const char* filename) if (!filename) return base; - char* path = GetCombinedPath(base, filename); + char* path = GetCombinedPathV(base, "%s", filename); + free(base); + return path; +} + +char* freerdp_GetConfigFilePath(BOOL system, const char* filename) +{ +#if defined(FREERDP_USE_VENDOR_PRODUCT_CONFIG_DIR) + const BOOL customized = TRUE; +#else + const BOOL customized = freerdp_areApplicationDetailsCustomized(); +#endif + if (customized) + return getCustomConfigPath(system, filename); + + eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; + + const char* vendor = freerdp_getApplicationDetailsVendor(); + const char* product = freerdp_getApplicationDetailsProduct(); + const SSIZE_T version = freerdp_getApplicationDetailsVersion(); + + if (!vendor || !product) + return NULL; + +#if !defined(WITH_FULL_CONFIG_PATH) + if (!system && (_stricmp(vendor, product) == 0)) + return freerdp_settings_get_legacy_config_path(filename); +#endif + + char* config = GetKnownPath(id); + if (!config) + return NULL; + + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); + + if (!base) + return NULL; + + if (!filename) + return base; + + char* path = GetCombinedPathV(base, "%s", filename); free(base); return path; } diff --git a/winpr/include/winpr/path.h b/winpr/include/winpr/path.h index 7d2878728..620cb4934 100644 --- a/winpr/include/winpr/path.h +++ b/winpr/include/winpr/path.h @@ -326,6 +326,37 @@ extern "C" WINPR_ATTR_NODISCARD WINPR_API char* winpr_GetConfigFilePath(BOOL system, const char* filename); + /** @brief Get a config file sub path with a formatting argument constructing the filename + * + * @param system \b TRUE to return a system config path + * @param filename The format string to generate the filename. Must not be \b NULL. Must not + * contain any forbidden characters. + * + * @return A (absolute) configuration file path or \b NULL in case of failure. + * @since version 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 3) + WINPR_API char* winpr_GetConfigFilePathV(BOOL system, WINPR_FORMAT_ARG const char* filename, + ...); + + /** @brief Get a config file sub path with a formatting argument constructing the filename + * + * @param system \b TRUE to return a system config path + * @param filename The format string to generate the filename. Must not be \b NULL. Must not + * contain any forbidden characters. + * @param ap The argument list + * + * @return A (absolute) configuration file path or \b NULL in case of failure. + * @since version 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 0) + WINPR_API char* winpr_GetConfigFilePathVA(BOOL system, WINPR_FORMAT_ARG const char* filename, + va_list ap); + WINPR_API const char* GetKnownPathIdString(int id); WINPR_ATTR_MALLOC(free, 1) @@ -336,6 +367,33 @@ extern "C" WINPR_ATTR_NODISCARD WINPR_API char* GetKnownSubPath(eKnownPathTypes id, const char* path); + /** @brief Append a path to some existing known path type. + * + * @param id a \ref eKnownPathTypes known path id + * @param path the format string generating the subpath. Must not be \b NULL + * + * @return A string of combined \b id path and \b path or \b NULL in case of an error. + * @since version 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 3) + WINPR_API char* GetKnownSubPathV(eKnownPathTypes id, const char* path, ...); + + /** @brief Append a path to some existing known path type. + * + * @param id a \ref eKnownPathTypes known path id + * @param path the format string generating the subpath. Must not be \b NULL + * @param ap a va_list containing the format string arguments + * * @return A string of combined \b basePath and \b path or \b NULL in case of an + * error. + * * @version since 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 0) + WINPR_API char* GetKnownSubPathVA(eKnownPathTypes id, const char* path, va_list ap); + WINPR_ATTR_MALLOC(free, 1) WINPR_ATTR_NODISCARD WINPR_API char* GetEnvironmentPath(char* name); @@ -344,10 +402,70 @@ extern "C" WINPR_ATTR_NODISCARD WINPR_API char* GetEnvironmentSubPath(char* name, const char* path); + /** @brief Append a path to some existing environment name. + * + * @param name The prefix path to use, must not be \b NULL + * @param path A format string used to generate the path to append. Must not be \b NULL + * + * @return A string of combined \b basePath and \b path or \b NULL in case of an error. + * @version since 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 3) + WINPR_API char* GetEnvironmentSubPathV(char* name, WINPR_FORMAT_ARG const char* path, ...); + + /** @brief Append a path to some existing environment name. + * + * @param name The prefix path to use, must not be \b NULL + * @param path A format string used to generate the path to append. Must not be \b NULL + * @param ap a va_list containing the format string arguments + * + * @return A string of combined \b basePath and \b path or \b NULL in case of an error. + * * @version since 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 0) + WINPR_API char* GetEnvironmentSubPathVA(char* name, WINPR_FORMAT_ARG const char* path, + va_list ap); + WINPR_ATTR_MALLOC(free, 1) WINPR_ATTR_NODISCARD WINPR_API char* GetCombinedPath(const char* basePath, const char* subPath); + /** @brief Append a path to some existing path. A system dependent path separator will be added + * automatically. + * + * @bug before version 3.23.0 the function did not allow subPath to be a format string. + * + * @param basePath The prefix path to use, must not be \b NULL + * @param subPathFmt A format string used to generate the path to append. Must not be \b NULL + * + * @return A string of combined \b basePath and \b subPathFmt or \b NULL in case of an error. + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 3) + WINPR_API char* GetCombinedPathV(const char* basePath, WINPR_FORMAT_ARG const char* subPathFmt, + ...); + + /** @brief Append a path to some existing path. A system dependent path separator will be added + * automatically. + * + * @param basePath The prefix path to use, must not be \b NULL + * @param subPathFmt A format string used to generate the path to append. Must not be \b NULL + * @param ap a va_list containing the format string arguments + * + * @return A string of combined \b basePath and \b subPathFmt or \b NULL in case of an error. + * @version since 3.23.0 + */ + WINPR_ATTR_MALLOC(free, 1) + WINPR_ATTR_NODISCARD + WINPR_ATTR_FORMAT_ARG(2, 0) + WINPR_API char* GetCombinedPathVA(const char* basePath, WINPR_FORMAT_ARG const char* subPathFmt, + va_list ap); + WINPR_API BOOL PathMakePathA(LPCSTR path, LPSECURITY_ATTRIBUTES lpAttributes); WINPR_API BOOL PathMakePathW(LPCWSTR path, LPSECURITY_ATTRIBUTES lpAttributes); diff --git a/winpr/include/winpr/winpr.h b/winpr/include/winpr/winpr.h index f760476ec..4bd20e046 100644 --- a/winpr/include/winpr/winpr.h +++ b/winpr/include/winpr/winpr.h @@ -21,6 +21,7 @@ #include #include +#include #ifdef __cplusplus extern "C" @@ -32,6 +33,53 @@ WINPR_API const char* winpr_get_version_string(void); WINPR_API const char* winpr_get_build_revision(void); WINPR_API const char* winpr_get_build_config(void); +/** @brief set \b vendor and \b product information for an application + * + * This sets the application details for an application instance. These values determine where + * to look for configuration files and other vendor/product specific settings data. + * + * @note When calling this function, the compile time options \b WINPR_USE_VENDOR_PRODUCT_CONFIG_DIR + * is ignored and the config path will always have the format 'vendor/product' or 'vendor/product1' + * (1 for the actual version set) + * + * @param vendor A vendor name to use. Must not be \b NULL. Must not contain forbidden + * filesystem symbols for any os. Must be less than \b MAX_PATH bytes. + * @param product A product name to use. Must not be \b NULL. Must not contain forbidden + * filesystem symbols for any os. Must be less than \b MAX_PATH bytes. + * @param version An optional versioning value to append to paths to settings. Use \b -1 to + * disable. + * + * @return \b TRUE if set successfully, \b FALSE in case of any error. + * @since version 3.23.0 + */ +WINPR_API WINPR_ATTR_NODISCARD BOOL winpr_setApplicationDetails(const char* vendor, + const char* product, + SSIZE_T version); + +/** @brief Get the current \b vendor string of the application. Defaults to \ref + * WINPR_VENDOR_STRING + * + * @return The current string set as \b vendor. + * @since version 3.23.0 + */ +WINPR_API WINPR_ATTR_NODISCARD const char* winpr_getApplicationDetailsVendor(void); + +/** @brief Get the current \b product string of the application. Defaults to \ref + * WINPR_PRODUCT_STRING + * + * @return The current string set as \b product. + * @since version 3.23.0 + */ +WINPR_API WINPR_ATTR_NODISCARD const char* winpr_getApplicationDetailsProduct(void); + +/** @brief Get the current \b version of the application. Defaults to \ref WINPR_API_VERSION + * if \b WITH_RESOURCE_VERSIONING is defined, otherwise \b -1 + * + * @return The current number set as \b version + * @since version 3.23.0 + */ +WINPR_API WINPR_ATTR_NODISCARD SSIZE_T winpr_getApplicationDetailsVersion(void); + #ifdef __cplusplus } #endif diff --git a/winpr/libwinpr/path/path.c b/winpr/libwinpr/path/path.c index 6cca837af..66afcd9d8 100644 --- a/winpr/libwinpr/path/path.c +++ b/winpr/libwinpr/path/path.c @@ -27,6 +27,8 @@ #include #include +#include "../utils.h" + #if defined(WITH_RESOURCE_VERSIONING) #define STR(x) #x #endif @@ -1218,32 +1220,47 @@ BOOL winpr_RemoveDirectory_RecursiveW(LPCWSTR lpPathName) return rc; } -char* winpr_GetConfigFilePath(BOOL system, const char* filename) +char* winpr_GetConfigFilePathVA(BOOL system, WINPR_FORMAT_ARG const char* filename, va_list ap) { eKnownPathTypes id = system ? KNOWN_PATH_SYSTEM_CONFIG_HOME : KNOWN_PATH_XDG_CONFIG_HOME; + const char* vendor = winpr_getApplicationDetailsVendor(); + const char* product = winpr_getApplicationDetailsProduct(); + const SSIZE_T version = winpr_getApplicationDetailsVersion(); -#if defined(WINPR_USE_VENDOR_PRODUCT_CONFIG_DIR) - char* vendor = GetKnownSubPath(id, WINPR_VENDOR_STRING); - if (!vendor) + if (!vendor || !product) return NULL; -#if defined(WITH_RESOURCE_VERSIONING) - const char* prod = WINPR_PRODUCT_STRING STR(WINPR_VERSION_MAJOR); -#else - const char* prod = WINPR_PRODUCT_STRING; -#endif - char* base = GetCombinedPath(vendor, prod); - free(vendor); -#else - char* base = GetKnownSubPath(id, "winpr"); -#endif + + char* config = GetKnownSubPathV(id, "%s", vendor); + if (!config) + return NULL; + + char* base = NULL; + if (version < 0) + base = GetCombinedPathV(config, "%s", product); + else + base = GetCombinedPathV(config, "%s%" PRIdz, product, version); + free(config); if (!base) return NULL; - if (!filename) - return base; - - char* path = GetCombinedPath(base, filename); + char* path = GetCombinedPathVA(base, filename, ap); free(base); return path; } + +char* winpr_GetConfigFilePath(BOOL system, const char* filename) +{ + if (!filename) + return winpr_GetConfigFilePathV(system, "%s", ""); + return winpr_GetConfigFilePathV(system, "%s", filename); +} + +char* winpr_GetConfigFilePathV(BOOL system, const char* filename, ...) +{ + va_list ap; + va_start(ap, filename); + char* str = winpr_GetConfigFilePathVA(system, filename, ap); + va_end(ap); + return str; +} diff --git a/winpr/libwinpr/path/shell.c b/winpr/libwinpr/path/shell.c index f647e8f08..2d7630741 100644 --- a/winpr/libwinpr/path/shell.c +++ b/winpr/libwinpr/path/shell.c @@ -393,12 +393,29 @@ char* GetKnownPath(eKnownPathTypes id) } char* GetKnownSubPath(eKnownPathTypes id, const char* path) +{ + if (!path) + return GetKnownSubPathV(id, "%s", ""); + return GetKnownSubPathV(id, "%s", path); +} + +char* GetKnownSubPathV(eKnownPathTypes id, const char* path, ...) +{ + va_list ap; + + va_start(ap, path); + char* str = GetKnownSubPathVA(id, path, ap); + va_end(ap); + return str; +} + +char* GetKnownSubPathVA(eKnownPathTypes id, const char* path, va_list ap) { char* knownPath = GetKnownPath(id); if (!knownPath) return NULL; - char* subPath = GetCombinedPath(knownPath, path); + char* subPath = GetCombinedPathVA(knownPath, path, ap); free(knownPath); return subPath; } @@ -431,19 +448,50 @@ char* GetEnvironmentPath(char* name) char* GetEnvironmentSubPath(char* name, const char* path) { - char* env = NULL; - char* subpath = NULL; - env = GetEnvironmentPath(name); + if (!path) + return GetEnvironmentSubPathV(name, "%s", ""); + return GetEnvironmentSubPathV(name, "%s", path); +} + +char* GetEnvironmentSubPathV(char* name, const char* path, ...) +{ + va_list ap; + va_start(ap, path); + char* str = GetEnvironmentSubPathVA(name, path, ap); + va_end(ap); + return str; +} + +char* GetEnvironmentSubPathVA(char* name, WINPR_FORMAT_ARG const char* path, va_list ap) +{ + char* env = GetEnvironmentPath(name); if (!env) return NULL; - subpath = GetCombinedPath(env, path); + char* subpath = GetCombinedPathVA(env, path, ap); free(env); return subpath; } -char* GetCombinedPath(const char* basePath, const char* subPath) +char* GetCombinedPath(const char* basePath, const char* subPathFmt) +{ + if (!subPathFmt) + return GetCombinedPathV(basePath, "%s", ""); + return GetCombinedPathV(basePath, "%s", subPathFmt); +} + +char* GetCombinedPathV(const char* basePath, const char* subPathFmt, ...) +{ + va_list ap; + + va_start(ap, subPathFmt); + char* str = GetCombinedPathVA(basePath, subPathFmt, ap); + va_end(ap); + return str; +} + +char* GetCombinedPathVA(const char* basePath, WINPR_FORMAT_ARG const char* subPathFmt, va_list ap) { HRESULT status = 0; char* subPathCpy = NULL; @@ -453,8 +501,20 @@ char* GetCombinedPath(const char* basePath, const char* subPath) if (basePath) basePathLength = strlen(basePath); - if (subPath) - subPathLength = strlen(subPath); + bool haveSubPath = subPathFmt && (*subPathFmt != '\0'); + if (haveSubPath) + { + const int rc = winpr_vasprintf(&subPathCpy, &subPathLength, subPathFmt, ap); + if (rc < 0) + return NULL; + if (rc == 0) + { + free(subPathCpy); + subPathCpy = NULL; + subPathLength = 0; + haveSubPath = false; + } + } const size_t length = basePathLength + subPathLength + 1; char* path = (char*)calloc(1, length + 1); @@ -468,11 +528,9 @@ char* GetCombinedPath(const char* basePath, const char* subPath) if (FAILED(PathCchConvertStyleA(path, basePathLength, PATH_STYLE_NATIVE))) goto fail; - if (!subPath) + if (!haveSubPath) return path; - subPathCpy = _strdup(subPath); - if (!subPathCpy) goto fail; diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.c b/winpr/libwinpr/sspi/NTLM/ntlm.c index c406fc7ab..acdcbc8bf 100644 --- a/winpr/libwinpr/sspi/NTLM/ntlm.c +++ b/winpr/libwinpr/sspi/NTLM/ntlm.c @@ -36,10 +36,12 @@ #include "ntlm_message.h" +#include "../../utils.h" + #include "../../log.h" #define TAG WINPR_TAG("sspi.NTLM") -#define WINPR_KEY "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\WinPR\\NTLM" +#define WINPR_KEY "Software\\%s\\WinPR\\NTLM" static char* NTLM_PACKAGE_NAME = "NTLM"; @@ -236,7 +238,6 @@ static int ntlm_SetContextTargetName(NTLM_CONTEXT* context, char* TargetName) static NTLM_CONTEXT* ntlm_ContextNew(void) { HKEY hKey = 0; - LONG status = 0; DWORD dwType = 0; DWORD dwSize = 0; DWORD dwValue = 0; @@ -252,58 +253,67 @@ static NTLM_CONTEXT* ntlm_ContextNew(void) context->SendWorkstationName = TRUE; context->NegotiateKeyExchange = TRUE; context->UseSamFileDatabase = TRUE; - status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WINPR_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - if (status == ERROR_SUCCESS) { - if (RegQueryValueEx(hKey, _T("NTLMv2"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == - ERROR_SUCCESS) - context->NTLMv2 = dwValue ? 1 : 0; - - if (RegQueryValueEx(hKey, _T("UseMIC"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == - ERROR_SUCCESS) - context->UseMIC = dwValue ? 1 : 0; - - if (RegQueryValueEx(hKey, _T("SendVersionInfo"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == - ERROR_SUCCESS) - context->SendVersionInfo = dwValue ? 1 : 0; - - if (RegQueryValueEx(hKey, _T("SendSingleHostData"), NULL, &dwType, (BYTE*)&dwValue, - &dwSize) == ERROR_SUCCESS) - context->SendSingleHostData = dwValue ? 1 : 0; - - if (RegQueryValueEx(hKey, _T("SendWorkstationName"), NULL, &dwType, (BYTE*)&dwValue, - &dwSize) == ERROR_SUCCESS) - context->SendWorkstationName = dwValue ? 1 : 0; - - if (RegQueryValueEx(hKey, _T("WorkstationName"), NULL, &dwType, NULL, &dwSize) == - ERROR_SUCCESS) + char* key = winpr_getApplicatonDetailsRegKey(WINPR_KEY); + if (key) { - char* workstation = (char*)malloc(dwSize + 1); + const LONG status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); - if (!workstation) + if (status == ERROR_SUCCESS) { - free(context); - return NULL; + if (RegQueryValueEx(hKey, _T("NTLMv2"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->NTLMv2 = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("UseMIC"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->UseMIC = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendVersionInfo"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendVersionInfo = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendSingleHostData"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendSingleHostData = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendWorkstationName"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendWorkstationName = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("WorkstationName"), NULL, &dwType, NULL, &dwSize) == + ERROR_SUCCESS) + { + char* workstation = (char*)malloc(dwSize + 1); + + if (!workstation) + { + free(context); + return NULL; + } + + const LONG rc = RegQueryValueExA(hKey, "WorkstationName", NULL, &dwType, + (BYTE*)workstation, &dwSize); + if (rc != ERROR_SUCCESS) + WLog_WARN(TAG, "Key ''WorkstationName' not found"); + workstation[dwSize] = '\0'; + + if (ntlm_SetContextWorkstation(context, workstation) < 0) + { + free(workstation); + free(context); + return NULL; + } + + free(workstation); + } + + RegCloseKey(hKey); } - - status = RegQueryValueExA(hKey, "WorkstationName", NULL, &dwType, (BYTE*)workstation, - &dwSize); - if (status != ERROR_SUCCESS) - WLog_WARN(TAG, "Key ''WorkstationName' not found"); - workstation[dwSize] = '\0'; - - if (ntlm_SetContextWorkstation(context, workstation) < 0) - { - free(workstation); - free(context); - return NULL; - } - - free(workstation); } - - RegCloseKey(hKey); } /* @@ -311,8 +321,9 @@ static NTLM_CONTEXT* ntlm_ContextNew(void) * but enabling it in WinPR breaks TS Gateway at this point */ context->SuppressExtendedProtection = FALSE; - status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\LSA"), 0, - KEY_READ | KEY_WOW64_64KEY, &hKey); + const LONG status = + RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\LSA"), 0, + KEY_READ | KEY_WOW64_64KEY, &hKey); if (status == ERROR_SUCCESS) { diff --git a/winpr/libwinpr/sspi/Negotiate/negotiate.c b/winpr/libwinpr/sspi/Negotiate/negotiate.c index 53c6987f6..c2e5b08b1 100644 --- a/winpr/libwinpr/sspi/Negotiate/negotiate.c +++ b/winpr/libwinpr/sspi/Negotiate/negotiate.c @@ -35,11 +35,11 @@ #include "../NTLM/ntlm_export.h" #include "../Kerberos/kerberos.h" #include "../sspi.h" +#include "../../utils.h" #include "../../log.h" #define TAG WINPR_TAG("negotiate") -static const char NEGO_REG_KEY[] = - "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\SSPI\\Negotiate"; +#define NEGO_REG_KEY "Software\\%s\\SSPI\\Negotiate" static const char PACKAGE_NAME_DISABLE_ALL[] = "none"; static const char PACKAGE_NAME_NTLM[] = "ntlm"; @@ -318,7 +318,6 @@ fail: static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm, BOOL* u2u) { HKEY hKey = NULL; - LONG rc = 0; WINPR_ASSERT(kerberos); WINPR_ASSERT(ntlm); @@ -342,16 +341,22 @@ static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm, BO return TRUE; // use explicit authentication package list } - rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, NEGO_REG_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); - if (rc == ERROR_SUCCESS) { - DWORD dwValue = 0; + char* key = winpr_getApplicatonDetailsRegKey(NEGO_REG_KEY); + if (key) + { + const LONG rc = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + free(key); + if (rc == ERROR_SUCCESS) + { + DWORD dwValue = 0; - if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS, &dwValue)) - *kerberos = (dwValue != 0) ? TRUE : FALSE; + if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS, &dwValue)) + *kerberos = (dwValue != 0) ? TRUE : FALSE; - if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS_U2U, &dwValue)) - *u2u = (dwValue != 0) ? TRUE : FALSE; + if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS_U2U, &dwValue)) + *u2u = (dwValue != 0) ? TRUE : FALSE; #if !defined(WITH_KRB5_NO_NTLM_FALLBACK) if (negotiate_get_dword(hKey, PACKAGE_NAME_NTLM, &dwValue)) @@ -359,6 +364,8 @@ static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm, BO #endif RegCloseKey(hKey); + } + } } return TRUE; diff --git a/winpr/libwinpr/utils.h b/winpr/libwinpr/utils.h new file mode 100644 index 000000000..7c7cfdaf2 --- /dev/null +++ b/winpr/libwinpr/utils.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Winpr internal helper functions + * + * Copyright 2026 Armin Novak + * Copyright 2026 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. + */ + +#pragma once + +#include + +WINPR_ATTR_MALLOC(free, 1) +WINPR_LOCAL WINPR_ATTR_NODISCARD char* winpr_getApplicatonDetailsRegKey(const char* fmt); + +WINPR_ATTR_MALLOC(free, 1) +WINPR_LOCAL WINPR_ATTR_NODISCARD char* winpr_getApplicatonDetailsCombined(char separator); + +WINPR_LOCAL WINPR_ATTR_NODISCARD BOOL winpr_areApplicationDetailsCustomized(void); diff --git a/winpr/libwinpr/utils/winpr.c b/winpr/libwinpr/utils/winpr.c index 1271464d4..8adcfe26b 100644 --- a/winpr/libwinpr/utils/winpr.c +++ b/winpr/libwinpr/utils/winpr.c @@ -29,11 +29,129 @@ #include #include #include +#include +#include + +#include "../utils.h" #if !defined(WIN32) #include #endif +static INIT_ONCE s_winpr_app_details_once = INIT_ONCE_STATIC_INIT; +static char s_winpr_vendor_string[MAX_PATH] = { 0 }; +static char s_winpr_product_string[MAX_PATH] = { 0 }; +static SSIZE_T s_winpr_version = -1; +static BOOL s_winpr_app_details_are_custom = FALSE; + +static BOOL CALLBACK init_app_details(WINPR_ATTR_UNUSED PINIT_ONCE once, + WINPR_ATTR_UNUSED PVOID param, + WINPR_ATTR_UNUSED PVOID* context) +{ + const size_t vlen = sizeof(WINPR_VENDOR_STRING); + const size_t plen = sizeof(WINPR_PRODUCT_STRING); + if (!strncpy(s_winpr_vendor_string, WINPR_VENDOR_STRING, vlen)) + return FALSE; + + if (!strncpy(s_winpr_product_string, WINPR_PRODUCT_STRING, plen)) + return FALSE; + +#if defined(WITH_RESOURCE_VERSIONING) + s_winpr_version = WINPR_API_VERSION; +#else + s_winpr_version = -1; +#endif + return TRUE; +} + +static WINPR_ATTR_NODISCARD BOOL initializeApplicationDetails(void) +{ + InitOnceExecuteOnce(&s_winpr_app_details_once, init_app_details, NULL, NULL); + return TRUE; +} + +BOOL winpr_setApplicationDetails(const char* vendor, const char* product, SSIZE_T version) +{ + if (!initializeApplicationDetails()) + return -1; + + if (!vendor || !product) + return FALSE; + const size_t vlen = strnlen(vendor, MAX_PATH); + const size_t plen = strnlen(product, MAX_PATH); + if ((vlen == MAX_PATH) || (plen == MAX_PATH)) + return FALSE; + + if (!strncpy(s_winpr_vendor_string, vendor, vlen + 1)) + return FALSE; + + if (!strncpy(s_winpr_product_string, product, plen + 1)) + return FALSE; + + s_winpr_version = version; + s_winpr_app_details_are_custom = TRUE; + return TRUE; +} + +const char* winpr_getApplicationDetailsVendor(void) +{ + if (!initializeApplicationDetails()) + return NULL; + return s_winpr_vendor_string; +} + +const char* winpr_getApplicationDetailsProduct(void) +{ + if (!initializeApplicationDetails()) + return NULL; + return s_winpr_product_string; +} + +char* winpr_getApplicatonDetailsRegKey(const char* fmt) +{ + char* val = winpr_getApplicatonDetailsCombined('\\'); + if (!val) + return NULL; + + char* str = NULL; + size_t slen = 0; + (void)winpr_asprintf(&str, &slen, fmt, val); + free(val); + return str; +} + +char* winpr_getApplicatonDetailsCombined(char separator) +{ + const SSIZE_T version = winpr_getApplicationDetailsVersion(); + const char* vendor = winpr_getApplicationDetailsVendor(); + const char* product = winpr_getApplicationDetailsProduct(); + + size_t slen = 0; + char* str = NULL; + if (version < 0) + { + (void)winpr_asprintf(&str, &slen, "%s%c%s", vendor, separator, product); + } + else + { + (void)winpr_asprintf(&str, &slen, "%s%c%s%" PRIdz, vendor, separator, product, version); + } + + return str; +} + +SSIZE_T winpr_getApplicationDetailsVersion(void) +{ + if (!initializeApplicationDetails()) + return -1; + return s_winpr_version; +} + +BOOL winpr_areApplicationDetailsCustomized(void) +{ + return s_winpr_app_details_are_custom; +} + void winpr_get_version(int* major, int* minor, int* revision) { if (major)