24#include <winpr/assert.h>
25#include <winpr/file.h>
26#include <winpr/handle.h>
27#include <winpr/json.h>
28#include <winpr/pipe.h>
29#include <winpr/string.h>
30#include <winpr/synch.h>
31#include <winpr/thread.h>
32#include <winpr/library.h>
33#include <winpr/path.h>
35#include <freerdp/build-config.h>
36#include <freerdp/utils/helpers.h>
37#include <freerdp/log.h>
38#include <freerdp/client/aad_helper.h>
40#define TAG CLIENT_TAG("common.aadauth")
44 rdpClientContext* context;
57WINPR_ATTR_MALLOC(free, 1)
58static
char* aad_auth_helper_detect_helper(
void);
61WINPR_ATTR_MALLOC(free, 1)
62static
char* build_hello_request(UINT32
id)
81WINPR_ATTR_MALLOC(free, 1)
82static
char* build_navigate_request(UINT32
id, const
char* title, const
char* url,
83 const
char* redirect_uri, const
char* user_agent,
99 if (ok && user_agent && (user_agent[0] !=
'\0'))
107WINPR_ATTR_MALLOC(free, 1)
108static
char* build_shutdown_request(UINT32
id)
123WINPR_ATTR_MALLOC(free, 1)
124static
char* build_exit_notification(
void)
140static BOOL helper_write_line(AadAuthHelper* helper,
const char* json)
142 WINPR_ASSERT(helper);
145 const size_t len = strlen(json);
146 const size_t total = len + 1;
147 char* line = malloc(total);
152 memcpy(line, json, len);
157 while (written < total)
160 if (!WriteFile(helper->hCmdInWrite, line + written, (DWORD)(total - written), &dwWritten,
164 WLog_ERR(TAG,
"aad-auth-helper: failed writing to helper");
168 written += dwWritten;
176WINPR_ATTR_MALLOC(free, 1)
177static
char* linebuf_extract(AadAuthHelper* helper)
179 if (!helper->buf || !helper->bufLen)
182 const BYTE* nl = memchr(helper->buf,
'\n', helper->bufLen);
186 const size_t lineLen = (size_t)(nl - helper->buf);
187 char* line = malloc(lineLen + 1);
190 memcpy(line, helper->buf, lineLen);
191 line[lineLen] =
'\0';
193 const size_t consumed = lineLen + 1;
194 const size_t remaining = helper->bufLen - consumed;
195 memmove(helper->buf, helper->buf + consumed, remaining);
196 helper->bufLen = remaining;
200WINPR_ATTR_MALLOC(free, 1)
201static
char* helper_read_line(AadAuthHelper* helper)
203 WINPR_ASSERT(helper);
205 char* line = linebuf_extract(helper);
213 if (!ReadFile(helper->hCmdOutRead, chunk,
sizeof(chunk), &dwRead,
nullptr) || (dwRead == 0))
215 WLog_ERR(TAG,
"aad-auth-helper: helper pipe closed or read error");
219 BYTE* nbuf = realloc(helper->buf, helper->bufLen + dwRead);
223 memcpy(helper->buf + helper->bufLen, chunk, dwRead);
224 helper->bufLen += dwRead;
226 line = linebuf_extract(helper);
236static WINPR_JSON* wait_for_response(AadAuthHelper* helper, UINT32 expectedId)
240 char* line = helper_read_line(helper);
248 WLog_WARN(TAG,
"aad-auth-helper: ignoring malformed line from helper");
256 if (m && (strcmp(m,
"log") == 0))
259 WINPR_JSON* message =
264 WLog_INFO(TAG,
"[helper] %s", text);
274 WLog_WARN(TAG,
"aad-auth-helper: dropping response with unexpected id");
283static void updateBoolFromConfig(WINPR_JSON* obj,
const char* what, BOOL* pVal)
296static void updateStringFromConfig(WINPR_JSON* obj,
const char* what,
char** pVal)
311static BOOL aad_auth_helper_config(AadAuthHelper* helper)
313 WINPR_ASSERT(helper);
316 BOOL useDetect = TRUE;
317 BOOL useUserConfig = TRUE;
319 const char config[] =
"freerdp-client-aad.json";
320 WINPR_JSON* sys = freerdp_GetJSONConfigFile(TRUE, config);
323 updateBoolFromConfig(sys,
"allow-commandline", &useArg);
324 updateBoolFromConfig(sys,
"allow-autodetect", &useDetect);
325 updateBoolFromConfig(sys,
"allow-user-config", &useUserConfig);
326 updateStringFromConfig(sys,
"helper-binary", &helper->exe);
327 updateStringFromConfig(sys,
"user-agent", &helper->userAgent);
332 WINPR_JSON* user = freerdp_GetJSONConfigFile(FALSE, config);
335 updateBoolFromConfig(user,
"allow-commandline", &useArg);
336 updateBoolFromConfig(user,
"allow-autodetect", &useDetect);
337 updateStringFromConfig(user,
"helper-binary", &helper->exe);
338 updateStringFromConfig(user,
"user-agent", &helper->userAgent);
347 if (args && (strcmp(
"autodetect", args) == 0))
350 helper->exe = aad_auth_helper_detect_helper();
357 helper->exe = _strdup(args);
366 FreeRDP_AadAuthUserAgent);
369 free(helper->userAgent);
370 helper->userAgent = _strdup(args);
371 if (!helper->userAgent)
376 if (useDetect && !helper->exe)
377 helper->exe = aad_auth_helper_detect_helper();
381 WLog_ERR(TAG,
"aad-auth-helper: no helper application detected, aborting");
389AadAuthHelper* aad_auth_helper_start(rdpClientContext* context)
391 WINPR_ASSERT(context);
393 AadAuthHelper* helper = calloc(1,
sizeof(AadAuthHelper));
396 helper->context = context;
399 LPPROC_THREAD_ATTRIBUTE_LIST attrList =
nullptr;
400 HANDLE hCmdInRead =
nullptr;
401 HANDLE hCmdOutWrite =
nullptr;
402 char* cmdline =
nullptr;
403 BOOL created = FALSE;
406 .bInheritHandle = TRUE,
407 .lpSecurityDescriptor =
nullptr };
410 .StartupInfo.cb =
sizeof(siStartInfoEx),
418 .StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE),
419 .StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE),
420 .StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE),
421 .StartupInfo.dwFlags = STARTF_USESTDHANDLES
424 if (!aad_auth_helper_config(helper))
427 if (!CreatePipe(&helper->hCmdOutRead, &hCmdOutWrite, &saAttr, 0))
429 WLog_ERR(TAG,
"aad-auth-helper: cmdOut CreatePipe failed");
432 if (!SetHandleInformation(helper->hCmdOutRead, HANDLE_FLAG_INHERIT, 0))
434 WLog_ERR(TAG,
"aad-auth-helper: cmdOut SetHandleInformation failed");
438 if (!CreatePipe(&hCmdInRead, &helper->hCmdInWrite, &saAttr, 0))
440 WLog_ERR(TAG,
"aad-auth-helper: cmdIn CreatePipe failed");
443 if (!SetHandleInformation(helper->hCmdInWrite, HANDLE_FLAG_INHERIT, 0))
445 WLog_ERR(TAG,
"aad-auth-helper: cmdIn SetHandleInformation failed");
449 char cmdInArg[64] = WINPR_C_ARRAY_INIT;
450 char cmdOutArg[64] = WINPR_C_ARRAY_INIT;
451 if (!winpr_exportHandleToString(hCmdInRead,
"--cmdInFd={}", cmdInArg,
sizeof(cmdInArg)))
453 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdIn handle");
456 if (!winpr_exportHandleToString(hCmdOutWrite,
"--cmdOutFd={}", cmdOutArg,
sizeof(cmdOutArg)))
458 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdOut handle");
478 HANDLE handles[5] = { siStartInfoEx.StartupInfo.hStdOutput, siStartInfoEx.StartupInfo.hStdInput,
479 siStartInfoEx.StartupInfo.hStdError, hCmdInRead, hCmdOutWrite };
483 if (InitializeProcThreadAttributeList(
nullptr, 1, 0, &size) || (size == 0))
485 WLog_ERR(TAG,
"aad-auth-helper: unexpected attribute list sizing result");
489 attrList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(size);
490 if (!attrList || !InitializeProcThreadAttributeList(attrList, 1, 0, &size))
492 WLog_ERR(TAG,
"aad-auth-helper: InitializeProcThreadAttributeList failed");
496 if (!UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
497 (PVOID)handles,
sizeof(handles),
nullptr,
nullptr))
499 WLog_ERR(TAG,
"aad-auth-helper: UpdateProcThreadAttribute failed");
503 siStartInfoEx.lpAttributeList = attrList;
507 size_t cmdlineLen = 0;
509 winpr_asprintf(&cmdline, &cmdlineLen,
"\"%s\" %s %s", helper->exe, cmdInArg, cmdOutArg);
514 CreateProcessA(
nullptr, cmdline,
nullptr,
nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT,
519 WLog_ERR(TAG,
"aad-auth-helper: failed to spawn '%s'", helper->exe);
525 DeleteProcThreadAttributeList(attrList);
528 (void)CloseHandle(procInfo.hThread);
530 (void)CloseHandle(hCmdInRead);
532 (void)CloseHandle(hCmdOutWrite);
536 aad_auth_helper_stop(helper);
540 helper->hProcess = procInfo.hProcess;
543 const UINT32
id = ++helper->nextId;
544 char* req = build_hello_request(
id);
545 BOOL ok = req && helper_write_line(helper, req);
550 WINPR_JSON* resp = wait_for_response(helper,
id);
558 WLog_ERR(TAG,
"aad-auth-helper: hello handshake failed");
559 aad_auth_helper_stop(helper);
567static AadAuthHelperNavigateStatus
568aad_auth_helper_navigate(AadAuthHelper* helper,
const char* title,
const char* url,
569 const char* redirect_uri,
const char* user_agent, UINT32 timeout_ms,
570 char** redirect_url,
size_t* redirect_url_len)
572 WINPR_ASSERT(helper);
574 WINPR_ASSERT(redirect_uri);
575 WINPR_ASSERT(redirect_url);
576 WINPR_ASSERT(redirect_url_len);
578 *redirect_url =
nullptr;
579 *redirect_url_len = 0;
581 const UINT32
id = ++helper->nextId;
583 build_navigate_request(
id, title ? title :
"", url, redirect_uri, user_agent, timeout_ms);
585 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
587 BOOL ok = helper_write_line(helper, req);
590 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
592 WINPR_JSON* resp = wait_for_response(helper,
id);
594 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
603 WLog_WARN(TAG,
"aad-auth-helper: navigate failed: %s", msg);
605 AadAuthHelperNavigateStatus status = AAD_AUTH_HELPER_NAVIGATE_ERROR;
606 if (strcmp(msg,
"user_cancelled") == 0)
607 status = AAD_AUTH_HELPER_NAVIGATE_CANCELLED;
608 else if (strcmp(msg,
"timeout") == 0)
609 status = AAD_AUTH_HELPER_NAVIGATE_TIMEOUT;
616 WINPR_JSON* urlItem =
623 WLog_ERR(TAG,
"aad-auth-helper: malformed navigate result");
625 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
628 *redirect_url = _strdup(value);
630 *redirect_url_len = strlen(*redirect_url);
632 return (*redirect_url !=
nullptr) ? AAD_AUTH_HELPER_NAVIGATE_OK
633 : AAD_AUTH_HELPER_NAVIGATE_ERROR;
636BOOL aad_auth_helper_is_running(
const AadAuthHelper* helper)
638 if (!helper || !helper->hProcess)
640 return WaitForSingleObject(helper->hProcess, 0) == WAIT_TIMEOUT;
643void aad_auth_helper_stop(AadAuthHelper* helper)
650 if (aad_auth_helper_is_running(helper))
652 const UINT32
id = ++helper->nextId;
653 char* req = build_shutdown_request(
id);
654 if (req && helper_write_line(helper, req))
656 WINPR_JSON* resp = wait_for_response(helper,
id);
662 char* notif = build_exit_notification();
664 (void)helper_write_line(helper, notif);
667 if (WaitForSingleObject(helper->hProcess, 3000) != WAIT_OBJECT_0)
669 WLog_WARN(TAG,
"aad-auth-helper: did not exit in time, terminating");
670 (void)TerminateProcess(helper->hProcess, 0);
674 if (helper->hProcess)
675 (void)CloseHandle(helper->hProcess);
677 if (helper->hCmdInWrite)
678 (void)CloseHandle(helper->hCmdInWrite);
679 if (helper->hCmdOutRead)
680 (void)CloseHandle(helper->hCmdOutRead);
684 free(helper->userAgent);
688WINPR_ATTR_MALLOC(winpr_zfree, 1)
689static
char* aad_auth_helper_extract_query_param(const
char* url, const
char* name)
694 const char* start = strchr(url,
'?');
698 const char* param = strstr(start, name);
702 const size_t len = strlen(name);
703 if (param[len] !=
'=')
706 char* str = _strdup(¶m[len + 1]);
710 char* end = strchr(str,
'&');
713 const size_t slen = strlen(str);
714 char* decoded = winpr_str_url_decode(str, slen);
727static AadAuthHelperNavigateStatus aad_helper_navigate(AadAuthHelper* helper,
const char* title,
728 const char* url,
char** pRedirectUrl,
729 size_t* pRedirectUrlLen)
731 WINPR_ASSERT(helper);
734 WINPR_ASSERT(pRedirectUrl);
735 WINPR_ASSERT(pRedirectUrlLen);
737 *pRedirectUrl =
nullptr;
738 *pRedirectUrlLen = 0;
740 char* redirectUri = aad_auth_helper_extract_query_param(url,
"redirect_uri");
743 WLog_ERR(TAG,
"[aad-auth] url %s has no redirect_uri parameter", url);
744 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
749 const char* user_agent = helper->userAgent;
750 const AadAuthHelperNavigateStatus status = aad_auth_helper_navigate(
751 helper, title, url, redirectUri, user_agent, 180000, &out, &outLen);
752 winpr_zfree(redirectUri);
753 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
760 *pRedirectUrlLen = outLen;
761 return AAD_AUTH_HELPER_NAVIGATE_OK;
765static BOOL aad_auth_helper_get_rdsaad_access_token(AadAuthHelper* helper,
766 freerdp_client_aad_type requestType,
767 freerdp_client_aad_type tokenType,
768 const char* scope,
const char* req_cnf,
771 WINPR_ASSERT(helper);
773 WINPR_ASSERT(req_cnf);
776 rdpClientContext* cctx = helper->context;
779 const char* title =
"FreeRDP WebView - AAD access token";
780 if (requestType == FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST)
781 title =
"FreeRDP WebView - AVD access token";
783 char* request = freerdp_client_get_aad_url(cctx, requestType, scope);
786 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not construct request");
790 char* redirectUrl =
nullptr;
791 size_t redirectUrlLen = 0;
792 const AadAuthHelperNavigateStatus status =
793 aad_helper_navigate(helper, title, request, &redirectUrl, &redirectUrlLen);
794 winpr_zfree(request);
796 if (status == AAD_AUTH_HELPER_NAVIGATE_CANCELLED)
798 winpr_znfree(redirectUrl, redirectUrlLen);
799 WLog_INFO(TAG,
"[aad-auth] user cancelled the authentication");
802 if (status == AAD_AUTH_HELPER_NAVIGATE_TIMEOUT)
804 winpr_znfree(redirectUrl, redirectUrlLen);
805 WLog_ERR(TAG,
"[aad-auth] authentication timed out");
808 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
810 winpr_znfree(redirectUrl, redirectUrlLen);
811 WLog_ERR(TAG,
"[aad-auth] authentication failed");
815 char* code = freerdp_client_extract_aad_code(cctx, redirectUrl, redirectUrlLen);
816 winpr_znfree(redirectUrl, redirectUrlLen);
820 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not find code parameter");
824 char* token_request =
nullptr;
825 if (tokenType == FREERDP_CLIENT_AAD_TOKEN_REQUEST)
826 token_request = freerdp_client_get_aad_url(cctx, tokenType, scope, code, req_cnf);
828 token_request = freerdp_client_get_aad_url(cctx, tokenType, code);
832 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not get token");
836 const BOOL rc = client_common_get_access_token(cctx->context.instance, token_request, token);
837 winpr_zfree(token_request);
841BOOL aad_auth_helper_get_access_token_v(AadAuthHelper* helper, AccessTokenType tokenType,
842 char** token,
size_t count, va_list args)
847 case ACCESS_TOKEN_TYPE_AAD:
852 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
859 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
862 const char* scope = va_arg(args,
const char*);
863 const char* req_cnf = va_arg(args,
const char*);
864 return aad_auth_helper_get_rdsaad_access_token(helper, FREERDP_CLIENT_AAD_AUTH_REQUEST,
865 FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope,
868 case ACCESS_TOKEN_TYPE_AVD:
871 "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
874 return aad_auth_helper_get_rdsaad_access_token(
875 helper, FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST, FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST,
878 WLog_ERR(TAG,
"Unexpected value for AccessTokenType [%" PRIu32
"], aborting",
884BOOL aad_auth_helper_get_access_token(AadAuthHelper* helper, AccessTokenType tokenType,
885 char** token,
size_t count, ...)
887 va_list ap = WINPR_C_ARRAY_INIT;
889 const BOOL rc = aad_auth_helper_get_access_token_v(helper, tokenType, token, count, ap);
904static const char* kHelperCandidates[] = {
"freerdp-xdg-aad-helper",
"freerdp-qt-aad-helper",
905 "freerdp-webview-aad-helper" };
907static void helper_binary_dirs_free(
char** dirs,
size_t count)
911 for (
size_t x = 0; x < count; x++)
919WINPR_ATTR_MALLOC(free, 1)
920static
char* aad_auth_helper_get_binary_dir(
void)
923 char* path =
nullptr;
926 char* tmp = realloc(path, len);
929 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
935 const DWORD rc = GetModuleFileNameA(
nullptr, path, len);
938 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
945 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
950 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
958 char* sep = strrchr(path,
'/');
960 char* sepWin = strrchr(path,
'\\');
961 if (!sep || (sepWin && (sepWin > sep)))
975WINPR_ATTR_MALLOC(helper_binary_dirs_free, 1)
976static
char** aad_auth_helper_binary_dirs(
size_t* count)
981 char** dirs = (
char**)calloc(32,
sizeof(
char*));
985 char* libexec = GetCombinedPath(FREERDP_INSTALL_PREFIX, FREERDP_LIBEXEC_PATH);
987 dirs[(*count)++] = libexec;
990 char* app = aad_auth_helper_get_binary_dir();
993 dirs[(*count)++] = app;
995 char* libexec = GetCombinedPath(app, FREERDP_LIBEXEC_REL_PATH);
997 dirs[(*count)++] = libexec;
1002WINPR_ATTR_MALLOC(free, 1)
1003static
char* aad_auth_helper_path_for_binary(const
char* dir, const
char* binaryName)
1005 const char extension[] = CMAKE_EXECUTABLE_SUFFIX;
1007 char* path =
nullptr;
1009 winpr_asprintf(&path, &plen,
"%s/%s%s", dir, binaryName, extension);
1015WINPR_ATTR_MALLOC(free, 1)
1016static
char* aad_auth_helper_auto_locate(
void)
1018 size_t dirscount = 0;
1019 char** dirs = aad_auth_helper_binary_dirs(&dirscount);
1023 char* path =
nullptr;
1024 for (
size_t i = 0; i < dirscount; i++)
1026 char* dir = dirs[i];
1029 for (
size_t x = 0; x < ARRAYSIZE(kHelperCandidates); x++)
1031 const char* binaryName = kHelperCandidates[x];
1032 char* cpath = aad_auth_helper_path_for_binary(dir, binaryName);
1033 if (winpr_PathFileExists(cpath))
1044 helper_binary_dirs_free(dirs, dirscount);
1051char* aad_auth_helper_detect_helper(
void)
1053 char* path = aad_auth_helper_auto_locate();
1057 WLog_ERR(TAG,
"[aad-auth] could not determine expected helper binary location");
1061 if (!winpr_PathFileExists(path))
1063 WLog_ERR(TAG,
"[aad-auth] helper binary not found at '%s'", path);
1068 WLog_DBG(TAG,
"[aad-auth] auto-detected helper %s", path);
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddObjectToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddObjectToObject.
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsBool(const WINPR_JSON *item)
Check if JSON item is of type BOOL.
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddIntegerToObject(WINPR_JSON *object, const char *name, int64_t number)
WINPR_JSON_AddIntegerToObject.
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsTrue(const WINPR_JSON *item)
Check if JSON item is BOOL value True.
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
WINPR_API char * WINPR_JSON_PrintUnformatted(WINPR_JSON *item)
Serialize a JSON instance to string without formatting for human readable formatted output see WINPR_...
WINPR_ATTR_NODISCARD WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
WINPR_ATTR_NODISCARD WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.