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;
54WINPR_ATTR_MALLOC(free, 1)
55static
char* aad_auth_helper_detect_helper(
void);
58WINPR_ATTR_MALLOC(free, 1)
59static
char* build_hello_request(UINT32
id)
78WINPR_ATTR_MALLOC(free, 1)
79static
char* build_navigate_request(UINT32
id, const
char* title, const
char* url,
80 const
char* redirect_uri, UINT32 timeout_ms)
101WINPR_ATTR_MALLOC(free, 1)
102static
char* build_shutdown_request(UINT32
id)
117WINPR_ATTR_MALLOC(free, 1)
118static
char* build_exit_notification(
void)
134static BOOL helper_write_line(AadAuthHelper* helper,
const char* json)
136 WINPR_ASSERT(helper);
139 const size_t len = strlen(json);
140 const size_t total = len + 1;
141 char* line = malloc(total);
146 memcpy(line, json, len);
151 while (written < total)
154 if (!WriteFile(helper->hCmdInWrite, line + written, (DWORD)(total - written), &dwWritten,
158 WLog_ERR(TAG,
"aad-auth-helper: failed writing to helper");
162 written += dwWritten;
170WINPR_ATTR_MALLOC(free, 1)
171static
char* linebuf_extract(AadAuthHelper* helper)
173 if (!helper->buf || !helper->bufLen)
176 const BYTE* nl = memchr(helper->buf,
'\n', helper->bufLen);
180 const size_t lineLen = (size_t)(nl - helper->buf);
181 char* line = malloc(lineLen + 1);
184 memcpy(line, helper->buf, lineLen);
185 line[lineLen] =
'\0';
187 const size_t consumed = lineLen + 1;
188 const size_t remaining = helper->bufLen - consumed;
189 memmove(helper->buf, helper->buf + consumed, remaining);
190 helper->bufLen = remaining;
194WINPR_ATTR_MALLOC(free, 1)
195static
char* helper_read_line(AadAuthHelper* helper)
197 WINPR_ASSERT(helper);
199 char* line = linebuf_extract(helper);
207 if (!ReadFile(helper->hCmdOutRead, chunk,
sizeof(chunk), &dwRead,
nullptr) || (dwRead == 0))
209 WLog_ERR(TAG,
"aad-auth-helper: helper pipe closed or read error");
213 BYTE* nbuf = realloc(helper->buf, helper->bufLen + dwRead);
217 memcpy(helper->buf + helper->bufLen, chunk, dwRead);
218 helper->bufLen += dwRead;
220 line = linebuf_extract(helper);
230static WINPR_JSON* wait_for_response(AadAuthHelper* helper, UINT32 expectedId)
234 char* line = helper_read_line(helper);
242 WLog_WARN(TAG,
"aad-auth-helper: ignoring malformed line from helper");
250 if (m && (strcmp(m,
"log") == 0))
253 WINPR_JSON* message =
258 WLog_INFO(TAG,
"[helper] %s", text);
268 WLog_WARN(TAG,
"aad-auth-helper: dropping response with unexpected id");
277static void updateBoolFromConfig(WINPR_JSON* obj,
const char* what, BOOL* pVal)
290static void updateStringFromConfig(WINPR_JSON* obj,
const char* what,
char** pVal)
304WINPR_ATTR_MALLOC(free, 1)
305static
char* getHelperBinary(const rdpClientContext* context)
318 BOOL useDetect = TRUE;
319 BOOL useUserConfig = TRUE;
321 const char config[] =
"freerdp-client-aad.json";
322 WINPR_JSON* sys = freerdp_GetJSONConfigFile(TRUE, config);
325 updateBoolFromConfig(sys,
"allow-commandline", &useArg);
326 updateBoolFromConfig(sys,
"allow-autodetect", &useDetect);
327 updateBoolFromConfig(sys,
"allow-user-config", &useUserConfig);
328 updateStringFromConfig(sys,
"helper-binary", &exe);
333 WINPR_JSON* user = freerdp_GetJSONConfigFile(FALSE, config);
336 updateBoolFromConfig(user,
"allow-commandline", &useArg);
337 updateBoolFromConfig(user,
"allow-autodetect", &useDetect);
338 updateStringFromConfig(user,
"helper-binary", &exe);
347 if (args && (strcmp(
"autodetect", args) == 0))
348 exe = aad_auth_helper_detect_helper();
353 if (!exe && useDetect)
354 exe = aad_auth_helper_detect_helper();
358 WLog_ERR(TAG,
"aad-auth-helper: no helper application detected, aborting");
366AadAuthHelper* aad_auth_helper_start(rdpClientContext* context)
368 WINPR_ASSERT(context);
371 AadAuthHelper* helper = calloc(1,
sizeof(AadAuthHelper));
374 helper->context = context;
377 LPPROC_THREAD_ATTRIBUTE_LIST attrList =
nullptr;
378 HANDLE hCmdInRead =
nullptr;
379 HANDLE hCmdOutWrite =
nullptr;
380 char* cmdline =
nullptr;
381 BOOL created = FALSE;
384 .bInheritHandle = TRUE,
385 .lpSecurityDescriptor =
nullptr };
388 .StartupInfo.cb =
sizeof(siStartInfoEx),
395 .StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE),
396 .StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE),
397 .StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE),
398 .StartupInfo.dwFlags = STARTF_USESTDHANDLES
401 if (!CreatePipe(&helper->hCmdOutRead, &hCmdOutWrite, &saAttr, 0))
403 WLog_ERR(TAG,
"aad-auth-helper: cmdOut CreatePipe failed");
406 if (!SetHandleInformation(helper->hCmdOutRead, HANDLE_FLAG_INHERIT, 0))
408 WLog_ERR(TAG,
"aad-auth-helper: cmdOut SetHandleInformation failed");
412 if (!CreatePipe(&hCmdInRead, &helper->hCmdInWrite, &saAttr, 0))
414 WLog_ERR(TAG,
"aad-auth-helper: cmdIn CreatePipe failed");
417 if (!SetHandleInformation(helper->hCmdInWrite, HANDLE_FLAG_INHERIT, 0))
419 WLog_ERR(TAG,
"aad-auth-helper: cmdIn SetHandleInformation failed");
423 char cmdInArg[64] = WINPR_C_ARRAY_INIT;
424 char cmdOutArg[64] = WINPR_C_ARRAY_INIT;
425 if (!winpr_exportHandleToString(hCmdInRead,
"--cmdInFd={}", cmdInArg,
sizeof(cmdInArg)))
427 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdIn handle");
430 if (!winpr_exportHandleToString(hCmdOutWrite,
"--cmdOutFd={}", cmdOutArg,
sizeof(cmdOutArg)))
432 WLog_ERR(TAG,
"aad-auth-helper: failed to export the cmdOut handle");
452 HANDLE handles[5] = { siStartInfoEx.StartupInfo.hStdOutput, siStartInfoEx.StartupInfo.hStdInput,
453 siStartInfoEx.StartupInfo.hStdError, hCmdInRead, hCmdOutWrite };
457 if (InitializeProcThreadAttributeList(
nullptr, 1, 0, &size) || (size == 0))
459 WLog_ERR(TAG,
"aad-auth-helper: unexpected attribute list sizing result");
463 attrList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(size);
464 if (!attrList || !InitializeProcThreadAttributeList(attrList, 1, 0, &size))
466 WLog_ERR(TAG,
"aad-auth-helper: InitializeProcThreadAttributeList failed");
470 if (!UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
471 (PVOID)handles,
sizeof(handles),
nullptr,
nullptr))
473 WLog_ERR(TAG,
"aad-auth-helper: UpdateProcThreadAttribute failed");
477 siStartInfoEx.lpAttributeList = attrList;
481 size_t cmdlineLen = 0;
482 exe = getHelperBinary(context);
486 winpr_asprintf(&cmdline, &cmdlineLen,
"\"%s\" %s %s", exe, cmdInArg, cmdOutArg);
491 CreateProcessA(
nullptr, cmdline,
nullptr,
nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT,
496 WLog_ERR(TAG,
"aad-auth-helper: failed to spawn '%s'", exe);
503 DeleteProcThreadAttributeList(attrList);
506 (void)CloseHandle(procInfo.hThread);
508 (void)CloseHandle(hCmdInRead);
510 (void)CloseHandle(hCmdOutWrite);
514 aad_auth_helper_stop(helper);
518 helper->hProcess = procInfo.hProcess;
521 const UINT32
id = ++helper->nextId;
522 char* req = build_hello_request(
id);
523 BOOL ok = req && helper_write_line(helper, req);
528 WINPR_JSON* resp = wait_for_response(helper,
id);
536 WLog_ERR(TAG,
"aad-auth-helper: hello handshake failed");
537 aad_auth_helper_stop(helper);
545static AadAuthHelperNavigateStatus aad_auth_helper_navigate(AadAuthHelper* helper,
546 const char* title,
const char* url,
547 const char* redirect_uri,
548 UINT32 timeout_ms,
char** redirect_url,
549 size_t* redirect_url_len)
551 WINPR_ASSERT(helper);
553 WINPR_ASSERT(redirect_uri);
554 WINPR_ASSERT(redirect_url);
555 WINPR_ASSERT(redirect_url_len);
557 *redirect_url =
nullptr;
558 *redirect_url_len = 0;
560 const UINT32
id = ++helper->nextId;
561 char* req = build_navigate_request(
id, title ? title :
"", url, redirect_uri, timeout_ms);
563 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
565 BOOL ok = helper_write_line(helper, req);
568 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
570 WINPR_JSON* resp = wait_for_response(helper,
id);
572 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
581 WLog_WARN(TAG,
"aad-auth-helper: navigate failed: %s", msg);
583 AadAuthHelperNavigateStatus status = AAD_AUTH_HELPER_NAVIGATE_ERROR;
584 if (strcmp(msg,
"user_cancelled") == 0)
585 status = AAD_AUTH_HELPER_NAVIGATE_CANCELLED;
586 else if (strcmp(msg,
"timeout") == 0)
587 status = AAD_AUTH_HELPER_NAVIGATE_TIMEOUT;
594 WINPR_JSON* urlItem =
601 WLog_ERR(TAG,
"aad-auth-helper: malformed navigate result");
603 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
606 *redirect_url = _strdup(value);
608 *redirect_url_len = strlen(*redirect_url);
610 return (*redirect_url !=
nullptr) ? AAD_AUTH_HELPER_NAVIGATE_OK
611 : AAD_AUTH_HELPER_NAVIGATE_ERROR;
614void aad_auth_helper_stop(AadAuthHelper* helper)
619 if (helper->hProcess)
621 const UINT32
id = ++helper->nextId;
622 char* req = build_shutdown_request(
id);
623 if (req && helper_write_line(helper, req))
625 WINPR_JSON* resp = wait_for_response(helper,
id);
631 char* notif = build_exit_notification();
633 (void)helper_write_line(helper, notif);
636 if (WaitForSingleObject(helper->hProcess, 3000) != WAIT_OBJECT_0)
638 WLog_WARN(TAG,
"aad-auth-helper: did not exit in time, terminating");
639 (void)TerminateProcess(helper->hProcess, 0);
641 (void)CloseHandle(helper->hProcess);
644 if (helper->hCmdInWrite)
645 (void)CloseHandle(helper->hCmdInWrite);
646 if (helper->hCmdOutRead)
647 (void)CloseHandle(helper->hCmdOutRead);
653WINPR_ATTR_MALLOC(winpr_zfree, 1)
654static
char* aad_auth_helper_extract_query_param(const
char* url, const
char* name)
659 const char* start = strchr(url,
'?');
663 const char* param = strstr(start, name);
667 const size_t len = strlen(name);
668 if (param[len] !=
'=')
671 char* str = _strdup(¶m[len + 1]);
675 char* end = strchr(str,
'&');
678 const size_t slen = strlen(str);
679 char* decoded = winpr_str_url_decode(str, slen);
692static AadAuthHelperNavigateStatus aad_helper_navigate(AadAuthHelper* helper,
const char* title,
693 const char* url,
char** pRedirectUrl,
694 size_t* pRedirectUrlLen)
696 WINPR_ASSERT(helper);
699 WINPR_ASSERT(pRedirectUrl);
700 WINPR_ASSERT(pRedirectUrlLen);
702 *pRedirectUrl =
nullptr;
703 *pRedirectUrlLen = 0;
705 char* redirectUri = aad_auth_helper_extract_query_param(url,
"redirect_uri");
708 WLog_ERR(TAG,
"[aad-auth] url %s has no redirect_uri parameter", url);
709 return AAD_AUTH_HELPER_NAVIGATE_ERROR;
714 const AadAuthHelperNavigateStatus status =
715 aad_auth_helper_navigate(helper, title, url, redirectUri, 180000, &out, &outLen);
716 winpr_zfree(redirectUri);
717 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
724 *pRedirectUrlLen = outLen;
725 return AAD_AUTH_HELPER_NAVIGATE_OK;
729static BOOL aad_auth_helper_get_rdsaad_access_token(AadAuthHelper* helper,
730 freerdp_client_aad_type requestType,
731 freerdp_client_aad_type tokenType,
732 const char* scope,
const char* req_cnf,
735 WINPR_ASSERT(helper);
737 WINPR_ASSERT(req_cnf);
740 rdpClientContext* cctx = helper->context;
743 const char* title =
"FreeRDP WebView - AAD access token";
744 if (requestType == FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST)
745 title =
"FreeRDP WebView - AVD access token";
747 char* request = freerdp_client_get_aad_url(cctx, requestType, scope);
750 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not construct request");
754 char* redirectUrl =
nullptr;
755 size_t redirectUrlLen = 0;
756 const AadAuthHelperNavigateStatus status =
757 aad_helper_navigate(helper, title, request, &redirectUrl, &redirectUrlLen);
758 winpr_zfree(request);
760 if (status == AAD_AUTH_HELPER_NAVIGATE_CANCELLED)
762 winpr_znfree(redirectUrl, redirectUrlLen);
763 WLog_INFO(TAG,
"[aad-auth] user cancelled the authentication");
766 if (status == AAD_AUTH_HELPER_NAVIGATE_TIMEOUT)
768 winpr_znfree(redirectUrl, redirectUrlLen);
769 WLog_ERR(TAG,
"[aad-auth] authentication timed out");
772 if (status != AAD_AUTH_HELPER_NAVIGATE_OK)
774 winpr_znfree(redirectUrl, redirectUrlLen);
775 WLog_ERR(TAG,
"[aad-auth] authentication failed");
779 char* code = freerdp_client_extract_aad_code(cctx, redirectUrl, redirectUrlLen);
780 winpr_znfree(redirectUrl, redirectUrlLen);
784 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not find code parameter");
788 char* token_request =
nullptr;
789 if (tokenType == FREERDP_CLIENT_AAD_TOKEN_REQUEST)
790 token_request = freerdp_client_get_aad_url(cctx, tokenType, scope, code, req_cnf);
792 token_request = freerdp_client_get_aad_url(cctx, tokenType, code);
796 WLog_ERR(TAG,
"[aad-auth] authentication failed, could not get token");
800 const BOOL rc = client_common_get_access_token(cctx->context.instance, token_request, token);
801 winpr_zfree(token_request);
805BOOL aad_auth_helper_get_access_token_v(AadAuthHelper* helper, AccessTokenType tokenType,
806 char** token,
size_t count, va_list args)
811 case ACCESS_TOKEN_TYPE_AAD:
816 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
823 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
826 const char* scope = va_arg(args,
const char*);
827 const char* req_cnf = va_arg(args,
const char*);
828 return aad_auth_helper_get_rdsaad_access_token(helper, FREERDP_CLIENT_AAD_AUTH_REQUEST,
829 FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope,
832 case ACCESS_TOKEN_TYPE_AVD:
835 "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
838 return aad_auth_helper_get_rdsaad_access_token(
839 helper, FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST, FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST,
842 WLog_ERR(TAG,
"Unexpected value for AccessTokenType [%" PRIu32
"], aborting",
848BOOL aad_auth_helper_get_access_token(AadAuthHelper* helper, AccessTokenType tokenType,
849 char** token,
size_t count, ...)
851 va_list ap = WINPR_C_ARRAY_INIT;
853 const BOOL rc = aad_auth_helper_get_access_token_v(helper, tokenType, token, count, ap);
868static const char* kHelperCandidates[] = {
"freerdp-xdg-aad-helper",
"freerdp-qt-aad-helper",
869 "freerdp-webview-aad-helper" };
871static void helper_binary_dirs_free(
char** dirs,
size_t count)
875 for (
size_t x = 0; x < count; x++)
883WINPR_ATTR_MALLOC(free, 1)
884static
char* aad_auth_helper_get_binary_dir(
void)
887 char* path =
nullptr;
890 char* tmp = realloc(path, len);
893 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
899 const DWORD rc = GetModuleFileNameA(
nullptr, path, len);
902 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
909 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
914 WLog_ERR(TAG,
"[aad-auth] GetModuleFileNameA failed");
922 char* sep = strrchr(path,
'/');
924 char* sepWin = strrchr(path,
'\\');
925 if (!sep || (sepWin && (sepWin > sep)))
939WINPR_ATTR_MALLOC(helper_binary_dirs_free, 1)
940static
char** aad_auth_helper_binary_dirs(
size_t* count)
945 char** dirs = (
char**)calloc(32,
sizeof(
char*));
949 char* libexec = GetCombinedPath(FREERDP_INSTALL_PREFIX, FREERDP_LIBEXEC_PATH);
951 dirs[(*count)++] = libexec;
954 char* app = aad_auth_helper_get_binary_dir();
957 dirs[(*count)++] = app;
959 char* libexec = GetCombinedPath(app, FREERDP_LIBEXEC_REL_PATH);
961 dirs[(*count)++] = libexec;
966WINPR_ATTR_MALLOC(free, 1)
967static
char* aad_auth_helper_path_for_binary(const
char* dir, const
char* binaryName)
969 const char extension[] = CMAKE_EXECUTABLE_SUFFIX;
971 char* path =
nullptr;
973 winpr_asprintf(&path, &plen,
"%s/%s%s", dir, binaryName, extension);
979WINPR_ATTR_MALLOC(free, 1)
980static
char* aad_auth_helper_auto_locate(
void)
982 size_t dirscount = 0;
983 char** dirs = aad_auth_helper_binary_dirs(&dirscount);
987 char* path =
nullptr;
988 for (
size_t i = 0; i < dirscount; i++)
993 for (
size_t x = 0; x < ARRAYSIZE(kHelperCandidates); x++)
995 const char* binaryName = kHelperCandidates[x];
996 char* cpath = aad_auth_helper_path_for_binary(dir, binaryName);
997 if (winpr_PathFileExists(cpath))
1008 helper_binary_dirs_free(dirs, dirscount);
1015char* aad_auth_helper_detect_helper(
void)
1017 char* path = aad_auth_helper_auto_locate();
1021 WLog_ERR(TAG,
"[aad-auth] could not determine expected helper binary location");
1025 if (!winpr_PathFileExists(path))
1027 WLog_ERR(TAG,
"[aad-auth] helper binary not found at '%s'", path);
1032 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.