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/utils/helpers.h>
36#include <freerdp/log.h>
37#include <freerdp/client/aad_helper.h>
39#define TAG CLIENT_TAG("common.aadauth")
43 rdpClientContext* context;
53WINPR_ATTR_MALLOC(free, 1)
54static
char* aad_auth_helper_detect_helper(
void);
57WINPR_ATTR_MALLOC(free, 1)
58static
char* build_hello_request(UINT32
id)
77WINPR_ATTR_MALLOC(free, 1)
78static
char* build_navigate_request(UINT32
id, const
char* title, const
char* url,
79 const
char* redirect_uri, UINT32 timeout_ms)
100WINPR_ATTR_MALLOC(free, 1)
101static
char* build_shutdown_request(UINT32
id)
116WINPR_ATTR_MALLOC(free, 1)
117static
char* build_exit_notification(
void)
133static BOOL helper_write_line(AadAuthHelper* helper,
const char* json)
135 WINPR_ASSERT(helper);
138 const size_t len = strlen(json);
139 const size_t total = len + 1;
140 char* line = malloc(total);
145 memcpy(line, json, len);
150 while (written < total)
153 if (!WriteFile(helper->hCmdInWrite, line + written, (DWORD)(total - written), &dwWritten,
157 WLog_ERR(TAG,
"aad-auth-helper: failed writing to helper");
161 written += dwWritten;
169WINPR_ATTR_MALLOC(free, 1)
170static
char* linebuf_extract(AadAuthHelper* helper)
172 if (!helper->buf || !helper->bufLen)
175 const BYTE* nl = memchr(helper->buf,
'\n', helper->bufLen);
179 const size_t lineLen = (size_t)(nl - helper->buf);
180 char* line = malloc(lineLen + 1);
183 memcpy(line, helper->buf, lineLen);
184 line[lineLen] =
'\0';
186 const size_t consumed = lineLen + 1;
187 const size_t remaining = helper->bufLen - consumed;
188 memmove(helper->buf, helper->buf + consumed, remaining);
189 helper->bufLen = remaining;
193WINPR_ATTR_MALLOC(free, 1)
194static
char* helper_read_line(AadAuthHelper* helper)
196 WINPR_ASSERT(helper);
198 char* line = linebuf_extract(helper);
206 if (!ReadFile(helper->hCmdOutRead, chunk,
sizeof(chunk), &dwRead,
nullptr) || (dwRead == 0))
208 WLog_ERR(TAG,
"aad-auth-helper: helper pipe closed or read error");
212 BYTE* nbuf = realloc(helper->buf, helper->bufLen + dwRead);
216 memcpy(helper->buf + helper->bufLen, chunk, dwRead);
217 helper->bufLen += dwRead;
219 line = linebuf_extract(helper);
229static WINPR_JSON* wait_for_response(AadAuthHelper* helper, UINT32 expectedId)
233 char* line = helper_read_line(helper);
241 WLog_WARN(TAG,
"aad-auth-helper: ignoring malformed line from helper");
249 if (m && (strcmp(m,
"log") == 0))
252 WINPR_JSON* message =
257 WLog_INFO(TAG,
"[helper] %s", text);
267 WLog_WARN(TAG,
"aad-auth-helper: dropping response with unexpected id");
276static void updateBoolFromConfig(WINPR_JSON* obj,
const char* what, BOOL* pVal)
289static void updateStringFromConfig(WINPR_JSON* obj,
const char* what,
char** pVal)
303WINPR_ATTR_MALLOC(free, 1)
304static
char* getHelperBinary(const rdpClientContext* context)
317 BOOL useDetect = TRUE;
318 BOOL useUserConfig = TRUE;
320 const char config[] =
"freerdp-client-aad.json";
321 WINPR_JSON* sys = freerdp_GetJSONConfigFile(TRUE, config);
324 updateBoolFromConfig(sys,
"allow-commandline", &useArg);
325 updateBoolFromConfig(sys,
"allow-autodetect", &useDetect);
326 updateBoolFromConfig(sys,
"allow-user-config", &useUserConfig);
327 updateStringFromConfig(sys,
"helper-binary", &exe);
332 WINPR_JSON* user = freerdp_GetJSONConfigFile(FALSE, config);
335 updateBoolFromConfig(sys,
"allow-commandline", &useArg);
336 updateBoolFromConfig(sys,
"allow-autodetect", &useDetect);
337 updateStringFromConfig(sys,
"helper-binary", &exe);
346 if (args && (strcmp(
"autodetect", args) == 0))
347 exe = aad_auth_helper_detect_helper();
352 if (!exe && useDetect)
353 exe = aad_auth_helper_detect_helper();
357 WLog_ERR(TAG,
"aad-auth-helper: no helper application detected, aborting");
365AadAuthHelper* aad_auth_helper_start(rdpClientContext* context)
367 WINPR_ASSERT(context);
370 AadAuthHelper* helper = calloc(1,
sizeof(AadAuthHelper));
373 helper->context = context;
376 LPPROC_THREAD_ATTRIBUTE_LIST attrList =
nullptr;
377 HANDLE hCmdInRead =
nullptr;
378 HANDLE hCmdOutWrite =
nullptr;
379 char* cmdline =
nullptr;
380 BOOL created = FALSE;
383 .bInheritHandle = TRUE,
384 .lpSecurityDescriptor =
nullptr };
387 .StartupInfo.cb =
sizeof(siStartInfoEx),
394 .StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE),
395 .StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE),
396 .StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE),
397 .StartupInfo.dwFlags = STARTF_USESTDHANDLES
400 if (!CreatePipe(&helper->hCmdOutRead, &hCmdOutWrite, &saAttr, 0))
402 WLog_ERR(TAG,
"aad-auth-helper: cmdOut CreatePipe failed");
405 if (!SetHandleInformation(helper->hCmdOutRead, HANDLE_FLAG_INHERIT, 0))
407 WLog_ERR(TAG,
"aad-auth-helper: cmdOut SetHandleInformation failed");
411 if (!CreatePipe(&hCmdInRead, &helper->hCmdInWrite, &saAttr, 0))
413 WLog_ERR(TAG,
"aad-auth-helper: cmdIn CreatePipe failed");
416 if (!SetHandleInformation(helper->hCmdInWrite, HANDLE_FLAG_INHERIT, 0))
418 WLog_ERR(TAG,
"aad-auth-helper: cmdIn SetHandleInformation failed");
422 char cmdInArg[64] = WINPR_C_ARRAY_INIT;
423 char cmdOutArg[64] = WINPR_C_ARRAY_INIT;
424 if (!winpr_exportHandleToString(hCmdInRead,
"--cmdInFd={}", cmdInArg,
sizeof(cmdInArg)))
426 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdIn handle");
429 if (!winpr_exportHandleToString(hCmdOutWrite,
"--cmdOutFd={}", cmdOutArg,
sizeof(cmdOutArg)))
431 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdOut handle");
451 HANDLE handles[5] = { siStartInfoEx.StartupInfo.hStdOutput, siStartInfoEx.StartupInfo.hStdInput,
452 siStartInfoEx.StartupInfo.hStdError, hCmdInRead, hCmdOutWrite };
456 if (InitializeProcThreadAttributeList(
nullptr, 1, 0, &size) || (size == 0))
458 WLog_ERR(TAG,
"aad-auth-helper: unexpected attribute list sizing result");
462 attrList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(size);
463 if (!attrList || !InitializeProcThreadAttributeList(attrList, 1, 0, &size))
465 WLog_ERR(TAG,
"aad-auth-helper: InitializeProcThreadAttributeList failed");
469 if (!UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
470 (PVOID)handles,
sizeof(handles),
nullptr,
nullptr))
472 WLog_ERR(TAG,
"aad-auth-helper: UpdateProcThreadAttribute failed");
476 siStartInfoEx.lpAttributeList = attrList;
480 size_t cmdlineLen = 0;
481 exe = getHelperBinary(context);
485 winpr_asprintf(&cmdline, &cmdlineLen,
"\"%s\" %s %s", exe, cmdInArg, cmdOutArg);
490 CreateProcessA(
nullptr, cmdline,
nullptr,
nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT,
495 WLog_ERR(TAG,
"aad-auth-helper: failed to spawn '%s'", exe);
502 DeleteProcThreadAttributeList(attrList);
505 (void)CloseHandle(procInfo.hThread);
507 (void)CloseHandle(hCmdInRead);
509 (void)CloseHandle(hCmdOutWrite);
513 aad_auth_helper_stop(helper);
517 helper->hProcess = procInfo.hProcess;
520 const UINT32
id = ++helper->nextId;
521 char* req = build_hello_request(
id);
522 BOOL ok = req && helper_write_line(helper, req);
527 WINPR_JSON* resp = wait_for_response(helper,
id);
535 WLog_ERR(TAG,
"aad-auth-helper: hello handshake failed");
536 aad_auth_helper_stop(helper);
544static AadAuthHelperNavigateStatus aad_auth_helper_navigate(AadAuthHelper* helper,
545 const char* title,
const char* url,
546 const char* redirect_uri,
547 UINT32 timeout_ms,
char** redirect_url,
548 size_t* redirect_url_len)
550 WINPR_ASSERT(helper);
552 WINPR_ASSERT(redirect_uri);
553 WINPR_ASSERT(redirect_url);
554 WINPR_ASSERT(redirect_url_len);
556 *redirect_url =
nullptr;
557 *redirect_url_len = 0;
559 const UINT32
id = ++helper->nextId;
560 char* req = build_navigate_request(
id, title ? title :
"", url, redirect_uri, timeout_ms);
562 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
564 BOOL ok = helper_write_line(helper, req);
567 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
569 WINPR_JSON* resp = wait_for_response(helper,
id);
571 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
580 WLog_WARN(TAG,
"aad-auth-helper: navigate failed: %s", msg);
582 AadAuthHelperNavigateStatus status = AAD_AUTH_HELPER_NAVIGATE_ERROR;
583 if (strcmp(msg,
"user_cancelled") == 0)
584 status = AAD_AUTH_HELPER_NAVIGATE_CANCELLED;
585 else if (strcmp(msg,
"timeout") == 0)
586 status = AAD_AUTH_HELPER_NAVIGATE_TIMEOUT;
593 WINPR_JSON* urlItem =
600 WLog_ERR(TAG,
"aad-auth-helper: malformed navigate result");
602 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
605 *redirect_url = _strdup(value);
607 *redirect_url_len = strlen(*redirect_url);
609 return (*redirect_url !=
nullptr) ? AAD_AUTH_HELPER_NAVIGATE_OK
610 : AAD_AUTH_HELPER_NAVIGATE_ERROR;
613void aad_auth_helper_stop(AadAuthHelper* helper)
618 if (helper->hProcess)
620 const UINT32
id = ++helper->nextId;
621 char* req = build_shutdown_request(
id);
622 if (req && helper_write_line(helper, req))
624 WINPR_JSON* resp = wait_for_response(helper,
id);
630 char* notif = build_exit_notification();
632 (void)helper_write_line(helper, notif);
635 if (WaitForSingleObject(helper->hProcess, 3000) != WAIT_OBJECT_0)
637 WLog_WARN(TAG,
"aad-auth-helper: did not exit in time, terminating");
638 (void)TerminateProcess(helper->hProcess, 0);
640 (void)CloseHandle(helper->hProcess);
643 if (helper->hCmdInWrite)
644 (void)CloseHandle(helper->hCmdInWrite);
645 if (helper->hCmdOutRead)
646 (void)CloseHandle(helper->hCmdOutRead);
652WINPR_ATTR_MALLOC(winpr_zfree, 1)
653static
char* aad_auth_helper_extract_query_param(const
char* url, const
char* name)
658 const char* start = strchr(url,
'?');
662 const char* param = strstr(start, name);
666 const size_t len = strlen(name);
667 if (param[len] !=
'=')
670 char* str = _strdup(¶m[len + 1]);
674 char* end = strchr(str,
'&');
677 const size_t slen = strlen(str);
678 char* decoded = winpr_str_url_decode(str, slen);
691static AadAuthHelperNavigateStatus aad_helper_navigate(AadAuthHelper* helper,
const char* title,
692 const char* url,
char** pRedirectUrl,
693 size_t* pRedirectUrlLen)
695 WINPR_ASSERT(helper);
698 WINPR_ASSERT(pRedirectUrl);
699 WINPR_ASSERT(pRedirectUrlLen);
701 *pRedirectUrl =
nullptr;
702 *pRedirectUrlLen = 0;
704 char* redirectUri = aad_auth_helper_extract_query_param(url,
"redirect_uri");
707 WLog_ERR(TAG,
"[aad-auth] url %s has no redirect_uri parameter", url);
708 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
713 const AadAuthHelperNavigateStatus status =
714 aad_auth_helper_navigate(helper, title, url, redirectUri, 180000, &out, &outLen);
715 winpr_zfree(redirectUri);
716 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
723 *pRedirectUrlLen = outLen;
724 return AAD_AUTH_HELPER_NAVIGATE_OK;
728static BOOL aad_auth_helper_get_rdsaad_access_token(AadAuthHelper* helper,
729 freerdp_client_aad_type requestType,
730 freerdp_client_aad_type tokenType,
731 const char* scope,
const char* req_cnf,
734 WINPR_ASSERT(helper);
736 WINPR_ASSERT(req_cnf);
739 rdpClientContext* cctx = helper->context;
742 const char* title =
"FreeRDP WebView - AAD access token";
743 if (requestType == FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST)
744 title =
"FreeRDP WebView - AVD access token";
746 char* request = freerdp_client_get_aad_url(cctx, requestType, scope);
749 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not construct request");
753 char* redirectUrl =
nullptr;
754 size_t redirectUrlLen = 0;
755 const AadAuthHelperNavigateStatus status =
756 aad_helper_navigate(helper, title, request, &redirectUrl, &redirectUrlLen);
757 winpr_zfree(request);
759 if (status == AAD_AUTH_HELPER_NAVIGATE_CANCELLED)
761 winpr_znfree(redirectUrl, redirectUrlLen);
762 WLog_INFO(TAG,
"[aad-auth] user cancelled the authentication");
765 if (status == AAD_AUTH_HELPER_NAVIGATE_TIMEOUT)
767 winpr_znfree(redirectUrl, redirectUrlLen);
768 WLog_ERR(TAG,
"[aad-auth] authentication timed out");
771 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
773 winpr_znfree(redirectUrl, redirectUrlLen);
774 WLog_ERR(TAG,
"[aad-auth] authentication failed");
778 char* code = freerdp_client_extract_aad_code(cctx, redirectUrl, redirectUrlLen);
779 winpr_znfree(redirectUrl, redirectUrlLen);
783 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not find code parameter");
787 char* token_request =
nullptr;
788 if (tokenType == FREERDP_CLIENT_AAD_TOKEN_REQUEST)
789 token_request = freerdp_client_get_aad_url(cctx, tokenType, scope, code, req_cnf);
791 token_request = freerdp_client_get_aad_url(cctx, tokenType, code);
795 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not get token");
799 const BOOL rc = client_common_get_access_token(cctx->context.instance, token_request, token);
800 winpr_zfree(token_request);
804BOOL aad_auth_helper_get_access_token_v(AadAuthHelper* helper, AccessTokenType tokenType,
805 char** token,
size_t count, va_list args)
810 case ACCESS_TOKEN_TYPE_AAD:
815 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
822 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
825 const char* scope = va_arg(args,
const char*);
826 const char* req_cnf = va_arg(args,
const char*);
827 return aad_auth_helper_get_rdsaad_access_token(helper, FREERDP_CLIENT_AAD_AUTH_REQUEST,
828 FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope,
831 case ACCESS_TOKEN_TYPE_AVD:
834 "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
837 return aad_auth_helper_get_rdsaad_access_token(
838 helper, FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST, FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST,
841 WLog_ERR(TAG,
"Unexpected value for AccessTokenType [%" PRIu32
"], aborting",
847BOOL aad_auth_helper_get_access_token(AadAuthHelper* helper, AccessTokenType tokenType,
848 char** token,
size_t count, ...)
850 va_list ap = WINPR_C_ARRAY_INIT;
852 const BOOL rc = aad_auth_helper_get_access_token_v(helper, tokenType, token, count, ap);
867static const char* kHelperCandidates[] = {
"freerdp-xdg-aad-helper",
"freerdp-qt-aad-helper",
868 "freerdp-webview-aad-helper" };
872WINPR_ATTR_MALLOC(free, 1)
873static
char* aad_auth_helper_binary_dir(
void)
876 char* path =
nullptr;
879 char* tmp = realloc(path, len);
882 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
888 const DWORD rc = GetModuleFileNameA(
nullptr, path, len);
891 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
898 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
903 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
911 char* sep = strrchr(path,
'/');
913 char* sepWin = strrchr(path,
'\\');
914 if (!sep || (sepWin && (sepWin > sep)))
926WINPR_ATTR_MALLOC(free, 1)
927static
char* aad_auth_helper_path_for_binary(const
char* dir, const
char* binaryName)
929 const char extension[] = CMAKE_EXECUTABLE_SUFFIX;
931 char* path =
nullptr;
933 winpr_asprintf(&path, &plen,
"%s/%s%s", dir, binaryName, extension);
939WINPR_ATTR_MALLOC(free, 1)
940static
char* aad_auth_helper_auto_locate(
void)
942 char* dir = aad_auth_helper_binary_dir();
946 for (
size_t x = 0; x < ARRAYSIZE(kHelperCandidates); x++)
948 const char* binaryName = kHelperCandidates[x];
949 char* path = aad_auth_helper_path_for_binary(dir, binaryName);
950 if (winpr_PathFileExists(path))
963char* aad_auth_helper_detect_helper(
void)
965 char* path = aad_auth_helper_auto_locate();
969 WLog_ERR(TAG,
"[aad-auth] could not determine expected helper binary location");
973 if (!winpr_PathFileExists(path))
975 WLog_ERR(TAG,
"[aad-auth] helper binary not found at '%s'", path);
980 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.