FreeRDP
Loading...
Searching...
No Matches
arm.c
1
21#include <freerdp/config.h>
22#include <freerdp/version.h>
23
24#include "../settings.h"
25
26#include <winpr/assert.h>
27
28#include <winpr/crt.h>
29#include <winpr/synch.h>
30#include <winpr/print.h>
31#include <winpr/stream.h>
32#include <winpr/winsock.h>
33#include <winpr/cred.h>
34#include <winpr/bcrypt.h>
35
36#include <freerdp/log.h>
37#include <freerdp/error.h>
38#include <freerdp/crypto/certificate.h>
39#include <freerdp/utils/ringbuffer.h>
40#include <freerdp/utils/smartcardlogon.h>
41#include <freerdp/utils/aad.h>
42
43#include "arm.h"
44#include "wst.h"
45#include "websocket.h"
46#include "http.h"
47#include "../credssp_auth.h"
48#include "../proxy.h"
49#include "../rdp.h"
50#include "../../crypto/crypto.h"
51#include "../../crypto/certificate.h"
52#include "../../crypto/opensslcompat.h"
53#include "rpc_fault.h"
54#include "../utils.h"
55#include "../redirection.h"
56
57#include <winpr/json.h>
58
59#include <string.h>
60
61struct rdp_arm
62{
63 rdpContext* context;
64 rdpTls* tls;
65 HttpContext* http;
66
67 UINT32 gateway_retry;
68 wLog* log;
69};
70
71typedef struct rdp_arm rdpArm;
72
73#define TAG FREERDP_TAG("core.gateway.arm")
74
75#ifdef WITH_AAD
76static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, UINT32 timeout)
77{
78 WINPR_ASSERT(arm);
79 WINPR_ASSERT(tls);
80 int sockfd = 0;
81 long status = 0;
82 BIO* socketBio = NULL;
83 BIO* bufferedBio = NULL;
84 rdpSettings* settings = arm->context->settings;
85 if (!settings)
86 return FALSE;
87
88 const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
89 if (!peerHostname)
90 return FALSE;
91
92 UINT16 peerPort = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
93 const char* proxyUsername = NULL;
94 const char* proxyPassword = NULL;
95 BOOL isProxyConnection =
96 proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
97
98 sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
99
100 WLog_Print(arm->log, WLOG_DEBUG, "connecting to %s %d", peerHostname, peerPort);
101 if (sockfd < 0)
102 return FALSE;
103
104 socketBio = BIO_new(BIO_s_simple_socket());
105
106 if (!socketBio)
107 {
108 closesocket((SOCKET)sockfd);
109 return FALSE;
110 }
111
112 BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
113 bufferedBio = BIO_new(BIO_s_buffered_socket());
114
115 if (!bufferedBio)
116 {
117 BIO_free_all(socketBio);
118 return FALSE;
119 }
120
121 bufferedBio = BIO_push(bufferedBio, socketBio);
122 if (!bufferedBio)
123 return FALSE;
124
125 status = BIO_set_nonblock(bufferedBio, TRUE);
126
127 if (isProxyConnection)
128 {
129 if (!proxy_connect(arm->context, bufferedBio, proxyUsername, proxyPassword,
130 freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
131 (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort)))
132 {
133 BIO_free_all(bufferedBio);
134 return FALSE;
135 }
136 }
137
138 if (!status)
139 {
140 BIO_free_all(bufferedBio);
141 return FALSE;
142 }
143
144 tls->hostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
145 tls->port = MIN(UINT16_MAX, WINPR_ASSERTING_INT_CAST(int32_t, settings->GatewayPort));
146 tls->isGatewayTransport = TRUE;
147 status = freerdp_tls_connect(tls, bufferedBio);
148 if (status < 1)
149 {
150 rdpContext* context = arm->context;
151 if (status < 0)
152 freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
153 else
154 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
155
156 return FALSE;
157 }
158 return (status >= 1);
159}
160
161static BOOL arm_fetch_wellknown(rdpArm* arm)
162{
163 WINPR_ASSERT(arm);
164 WINPR_ASSERT(arm->context);
165 WINPR_ASSERT(arm->context->rdp);
166
167 rdpRdp* rdp = arm->context->rdp;
168 if (rdp->wellknown)
169 return TRUE;
170
171 const char* base =
172 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayAzureActiveDirectory);
173 const BOOL useTenant =
174 freerdp_settings_get_bool(arm->context->settings, FreeRDP_GatewayAvdUseTenantid);
175 const char* tenantid = "common";
176 if (useTenant)
177 tenantid =
178 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayAvdAadtenantid);
179
180 rdp->wellknown = freerdp_utils_aad_get_wellknown(arm->log, base, tenantid);
181 return rdp->wellknown ? TRUE : FALSE;
182}
183
184static wStream* arm_build_http_request(rdpArm* arm, const char* method,
185 TRANSFER_ENCODING transferEncoding, const char* content_type,
186 size_t content_length)
187{
188 wStream* s = NULL;
189
190 WINPR_ASSERT(arm);
191 WINPR_ASSERT(method);
192 WINPR_ASSERT(content_type);
193
194 WINPR_ASSERT(arm->context);
195 WINPR_ASSERT(arm->context->rdp);
196
197 const char* uri = http_context_get_uri(arm->http);
198 HttpRequest* request = http_request_new();
199
200 if (!request)
201 return NULL;
202
203 rdpSettings* settings = arm->context->settings;
204
205 if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
206 goto out;
207
208 if (!freerdp_settings_get_string(settings, FreeRDP_GatewayHttpExtAuthBearer))
209 {
210 char* token = NULL;
211
212 pGetCommonAccessToken GetCommonAccessToken = freerdp_get_common_access_token(arm->context);
213 if (!GetCommonAccessToken)
214 {
215 WLog_Print(arm->log, WLOG_ERROR, "No authorization token provided");
216 goto out;
217 }
218
219 if (!arm_fetch_wellknown(arm))
220 goto out;
221
222 if (!GetCommonAccessToken(arm->context, ACCESS_TOKEN_TYPE_AVD, &token, 0))
223 {
224 WLog_Print(arm->log, WLOG_ERROR, "Unable to obtain access token");
225 goto out;
226 }
227
228 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, token))
229 {
230 free(token);
231 goto out;
232 }
233 free(token);
234 }
235
236 if (!http_request_set_auth_scheme(request, "Bearer") ||
237 !http_request_set_auth_param(
238 request, freerdp_settings_get_string(settings, FreeRDP_GatewayHttpExtAuthBearer)))
239 goto out;
240
241 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
242 !http_request_set_content_length(request, content_length) ||
243 !http_request_set_content_type(request, content_type))
244 goto out;
245
246 s = http_request_write(arm->http, request);
247out:
248 http_request_free(request);
249
250 if (s)
251 Stream_SealLength(s);
252
253 return s;
254}
255
256static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls, const char* method,
257 const char* content_type, const char* data, size_t content_length)
258{
259 int status = -1;
260 wStream* s =
261 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
262
263 if (!s)
264 return FALSE;
265
266 const size_t sz = Stream_Length(s);
267 WLog_Print(arm->log, WLOG_TRACE, "header [%" PRIuz "]: %s", sz, Stream_Buffer(s));
268 WLog_Print(arm->log, WLOG_TRACE, "body [%" PRIuz "]: %s", content_length, data);
269 status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
270
271 Stream_Free(s, TRUE);
272 if (status >= 0 && content_length > 0 && data)
273 status = freerdp_tls_write_all(tls, (const BYTE*)data, content_length);
274
275 return (status >= 0);
276}
277
278static void arm_free(rdpArm* arm)
279{
280 if (!arm)
281 return;
282
283 freerdp_tls_free(arm->tls);
284 http_context_free(arm->http);
285
286 free(arm);
287}
288
289static rdpArm* arm_new(rdpContext* context)
290{
291 WINPR_ASSERT(context);
292
293 rdpArm* arm = (rdpArm*)calloc(1, sizeof(rdpArm));
294 if (!arm)
295 goto fail;
296
297 arm->log = WLog_Get(TAG);
298 arm->context = context;
299 arm->tls = freerdp_tls_new(context);
300 if (!arm->tls)
301 goto fail;
302
303 arm->http = http_context_new();
304
305 if (!arm->http)
306 goto fail;
307
308 return arm;
309
310fail:
311 arm_free(arm);
312 return NULL;
313}
314
315static char* arm_create_request_json(rdpArm* arm)
316{
317 char* lbi = NULL;
318 char* message = NULL;
319
320 WINPR_ASSERT(arm);
321
322 WINPR_JSON* json = WINPR_JSON_CreateObject();
323 if (!json)
324 goto arm_create_cleanup;
326 json, "application",
327 freerdp_settings_get_string(arm->context->settings, FreeRDP_RemoteApplicationProgram));
328
329 lbi = calloc(
330 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength) + 1,
331 sizeof(char));
332 if (!lbi)
333 goto arm_create_cleanup;
334
335 {
336 const size_t len =
337 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength);
338 memcpy(lbi, freerdp_settings_get_pointer(arm->context->settings, FreeRDP_LoadBalanceInfo),
339 len);
340 }
341
342 WINPR_JSON_AddStringToObject(json, "loadBalanceInfo", lbi);
343 WINPR_JSON_AddNullToObject(json, "LogonToken");
344 WINPR_JSON_AddNullToObject(json, "gatewayLoadBalancerToken");
345
346 message = WINPR_JSON_PrintUnformatted(json);
347arm_create_cleanup:
348 if (json)
349 WINPR_JSON_Delete(json);
350 free(lbi);
351 return message;
352}
353
368static WINPR_CIPHER_CTX* treatAuthBlob(wLog* log, const BYTE* pbInput, size_t cbInput,
369 size_t* pBlockSize)
370{
371 WINPR_CIPHER_CTX* ret = NULL;
372 char algoName[100] = { 0 };
373
374 WINPR_ASSERT(pBlockSize);
375 SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
376 sizeof(algoName) - 1);
377 if (algoSz <= 0)
378 {
379 WLog_Print(log, WLOG_ERROR, "invalid algoName");
380 return NULL;
381 }
382
383 if (strcmp(algoName, "AES") != 0)
384 {
385 WLog_Print(log, WLOG_ERROR, "only AES is supported for now");
386 return NULL;
387 }
388
389 *pBlockSize = WINPR_AES_BLOCK_SIZE;
390 const size_t algoLen = WINPR_ASSERTING_INT_CAST(size_t, (algoSz + 1)) * sizeof(WCHAR);
391 if (cbInput < algoLen)
392 {
393 WLog_Print(log, WLOG_ERROR, "invalid AuthBlob size");
394 return NULL;
395 }
396
397 cbInput -= algoLen;
398
399 /* BCRYPT_KEY_DATA_BLOB_HEADER */
400 wStream staticStream = { 0 };
401 wStream* s = Stream_StaticConstInit(&staticStream, &pbInput[algoLen], cbInput);
402
403 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 12))
404 return NULL;
405
406 const UINT32 dwMagic = Stream_Get_UINT32(s);
407 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
408 {
409 WLog_Print(log, WLOG_ERROR, "unsupported authBlob type");
410 return NULL;
411 }
412
413 const UINT32 dwVersion = Stream_Get_UINT32(s);
414 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
415 {
416 WLog_Print(log, WLOG_ERROR, "unsupported authBlob version %" PRIu32 ", expecting %d",
417 dwVersion, BCRYPT_KEY_DATA_BLOB_VERSION1);
418 return NULL;
419 }
420
421 const UINT32 cbKeyData = Stream_Get_UINT32(s);
422 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, cbKeyData))
423 {
424 WLog_Print(log, WLOG_ERROR, "invalid authBlob size");
425 return NULL;
426 }
427
428 WINPR_CIPHER_TYPE cipherType = WINPR_CIPHER_NONE;
429 switch (cbKeyData)
430 {
431 case 16:
432 cipherType = WINPR_CIPHER_AES_128_CBC;
433 break;
434 case 24:
435 cipherType = WINPR_CIPHER_AES_192_CBC;
436 break;
437 case 32:
438 cipherType = WINPR_CIPHER_AES_256_CBC;
439 break;
440 default:
441 WLog_Print(log, WLOG_ERROR, "invalid authBlob cipher size");
442 return NULL;
443 }
444
445 ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
446 if (!ret)
447 {
448 WLog_Print(log, WLOG_ERROR, "error creating cipher");
449 return NULL;
450 }
451
452 if (!winpr_Cipher_SetPadding(ret, TRUE))
453 {
454 WLog_Print(log, WLOG_ERROR, "unable to enable padding on cipher");
455 winpr_Cipher_Free(ret);
456 return NULL;
457 }
458
459 return ret;
460}
461
462static BOOL arm_stringEncodeW(const BYTE* pin, size_t cbIn, BYTE** ppOut, size_t* pcbOut)
463{
464 *ppOut = NULL;
465 *pcbOut = 0;
466
467 /* encode to base64 with crlf */
468 char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
469 if (!b64encoded)
470 return FALSE;
471
472 /* and then convert to Unicode */
473 size_t outSz = 0;
474 *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
475 free(b64encoded);
476
477 if (!*ppOut)
478 return FALSE;
479
480 *pcbOut = (outSz + 1) * sizeof(WCHAR);
481 return TRUE;
482}
483
484static BOOL arm_encodeRedirectPasswd(wLog* log, rdpSettings* settings, const rdpCertificate* cert,
485 WINPR_CIPHER_CTX* cipher, size_t blockSize)
486{
487 BOOL ret = FALSE;
488 BYTE* output = NULL;
489 BYTE* finalOutput = NULL;
490
491 /* let's prepare the encrypted password, first we do a
492 * cipheredPass = AES(redirectedAuthBlob, toUtf16(passwd))
493 */
494
495 size_t wpasswdLen = 0;
496 WCHAR* wpasswd = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &wpasswdLen);
497 if (!wpasswd)
498 {
499 WLog_Print(log, WLOG_ERROR, "error when converting password to UTF16");
500 return FALSE;
501 }
502
503 const size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
504 BYTE* encryptedPass = calloc(1, wpasswdBytes + blockSize); /* 16: block size of AES (padding) */
505
506 if (!encryptedPass)
507 goto out;
508
509 {
510 size_t encryptedPassLen = 0;
511 if (!winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen))
512 goto out;
513
514 if (encryptedPassLen > wpasswdBytes)
515 goto out;
516
517 {
518 size_t finalLen = 0;
519 if (!winpr_Cipher_Final(cipher, &encryptedPass[encryptedPassLen], &finalLen))
520 {
521 WLog_Print(log, WLOG_ERROR, "error when ciphering password");
522 goto out;
523 }
524 encryptedPassLen += finalLen;
525 }
526
527 /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
528 {
529 size_t output_length = 0;
530 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen,
531 &output, &output_length))
532 {
533 WLog_Print(log, WLOG_ERROR, "unable to encrypt with the server's public key");
534 goto out;
535 }
536
537 {
538 size_t finalOutputLen = 0;
539 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
540 {
541 WLog_Print(log, WLOG_ERROR, "unable to base64+utf16 final blob");
542 goto out;
543 }
544
545 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword,
546 finalOutput, finalOutputLen))
547 {
548 WLog_Print(log, WLOG_ERROR,
549 "unable to set the redirection password in settings");
550 goto out;
551 }
552 }
553 }
554 }
555
556 settings->RdstlsSecurity = TRUE;
557 settings->AadSecurity = FALSE;
558 settings->NlaSecurity = FALSE;
559 settings->RdpSecurity = FALSE;
560 settings->TlsSecurity = FALSE;
561 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
562 ret = TRUE;
563out:
564 free(finalOutput);
565 free(output);
566 free(encryptedPass);
567 free(wpasswd);
568 return ret;
569}
570
575static BOOL arm_pick_base64Utf16Field(wLog* log, const WINPR_JSON* json, const char* name,
576 BYTE** poutput, size_t* plen)
577{
578 *poutput = NULL;
579 *plen = 0;
580
581 WINPR_JSON* node = WINPR_JSON_GetObjectItemCaseSensitive(json, name);
582 if (!node || !WINPR_JSON_IsString(node))
583 return TRUE;
584
585 const char* nodeValue = WINPR_JSON_GetStringValue(node);
586 if (!nodeValue)
587 return TRUE;
588
589 BYTE* output1 = NULL;
590 size_t len1 = 0;
591 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
592 if (!output1 || !len1)
593 {
594 WLog_Print(log, WLOG_ERROR, "error when first unbase64 for %s", name);
595 free(output1);
596 return FALSE;
597 }
598
599 size_t len2 = 0;
600 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
601 free(output1);
602 if (!output2 || !len2)
603 {
604 WLog_Print(log, WLOG_ERROR, "error when decode('utf-16') for %s", name);
605 free(output2);
606 return FALSE;
607 }
608
609 BYTE* output = NULL;
610 crypto_base64_decode(output2, len2, &output, plen);
611 free(output2);
612 if (!output || !*plen)
613 {
614 WLog_Print(log, WLOG_ERROR, "error when second unbase64 for %s", name);
615 free(output);
616 return FALSE;
617 }
618
619 *poutput = output;
620 return TRUE;
621}
622
643static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
644{
645 WINPR_ASSERT(ipvX);
646 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipvX, "ipAddress");
647 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
648 return 0;
649
650 /* Each entry might contain a public and a private address */
651 return WINPR_JSON_GetArraySize(ipAddress) * 2;
652}
653
654static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAddressIdx)
655{
656 WINPR_ASSERT(settings);
657 WINPR_ASSERT(ipv6);
658 WINPR_ASSERT(pAddressIdx);
659
660 if (!freerdp_settings_get_bool(settings, FreeRDP_IPv6Enabled))
661 return TRUE;
662
663 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv6, "ipAddress");
664 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
665 return TRUE;
666 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
667 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
668 for (size_t j = 0; j < naddresses; j++)
669 {
670 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
671 if (!adressN || !WINPR_JSON_IsString(adressN))
672 continue;
673
674 const char* addr = WINPR_JSON_GetStringValue(adressN);
675 if (utils_str_is_empty(addr))
676 continue;
677
678 if (*pAddressIdx >= count)
679 {
680 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
681 return FALSE;
682 }
683
684 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
685 (*pAddressIdx)++, addr))
686 return FALSE;
687 }
688 return TRUE;
689}
690
691static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAddressIdx)
692{
693 WINPR_ASSERT(settings);
694 WINPR_ASSERT(ipv4);
695 WINPR_ASSERT(pAddressIdx);
696
697 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv4, "ipAddress");
698 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
699 return TRUE;
700
701 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
702 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
703 for (size_t j = 0; j < naddresses; j++)
704 {
705 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
706 if (!adressN)
707 continue;
708
709 WINPR_JSON* publicIpNode =
710 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "publicIpAddress");
711 if (publicIpNode && WINPR_JSON_IsString(publicIpNode))
712 {
713 const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode);
714 if (!utils_str_is_empty(publicIp))
715 {
716 if (*pAddressIdx >= count)
717 {
718 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
719 return FALSE;
720 }
721 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
722 (*pAddressIdx)++, publicIp))
723 return FALSE;
724 }
725 }
726
727 WINPR_JSON* privateIpNode =
728 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "privateIpAddress");
729 if (privateIpNode && WINPR_JSON_IsString(privateIpNode))
730 {
731 const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode);
732 if (!utils_str_is_empty(privateIp))
733 {
734 if (*pAddressIdx >= count)
735 {
736 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
737 return FALSE;
738 }
739 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
740 (*pAddressIdx)++, privateIp))
741 return FALSE;
742 }
743 }
744 }
745 return TRUE;
746}
747
748static BOOL arm_treat_azureInstanceNetworkMetadata(wLog* log, const char* metadata,
749 rdpSettings* settings)
750{
751 BOOL ret = FALSE;
752
753 WINPR_ASSERT(settings);
754
755 if (!freerdp_target_net_adresses_reset(settings, 0))
756 return FALSE;
757
758 WINPR_JSON* json = WINPR_JSON_Parse(metadata);
759 if (!json)
760 {
761 WLog_Print(log, WLOG_ERROR, "invalid azureInstanceNetworkMetadata");
762 return FALSE;
763 }
764
765 WINPR_JSON* iface = WINPR_JSON_GetObjectItemCaseSensitive(json, "interface");
766 if (!iface)
767 {
768 ret = TRUE;
769 goto out;
770 }
771
772 if (!WINPR_JSON_IsArray(iface))
773 {
774 WLog_Print(log, WLOG_ERROR, "expecting interface to be an Array");
775 goto out;
776 }
777
778 {
779 const size_t interfaceSz = WINPR_JSON_GetArraySize(iface);
780 if (interfaceSz == 0)
781 {
782 WLog_WARN(TAG, "no addresses in azure instance metadata");
783 ret = TRUE;
784 goto out;
785 }
786
787 {
788 size_t count = 0;
789 for (size_t i = 0; i < interfaceSz; i++)
790 {
791 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
792 if (!interN)
793 continue;
794
795 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
796 if (ipv6)
797 count += arm_parse_ipvx_count(ipv6);
798
799 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
800 if (ipv4)
801 count += arm_parse_ipvx_count(ipv4);
802 }
803
804 if (!freerdp_target_net_adresses_reset(settings, count))
805 return FALSE;
806 }
807
808 {
809 size_t addressIdx = 0;
810 for (size_t i = 0; i < interfaceSz; i++)
811 {
812 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
813 if (!interN)
814 continue;
815
816 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
817 if (ipv6)
818 {
819 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
820 goto out;
821 }
822
823 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
824 if (ipv4)
825 {
826 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
827 goto out;
828 }
829 }
830 if (addressIdx > UINT32_MAX)
831 goto out;
832
833 if (!freerdp_settings_set_uint32(settings, FreeRDP_TargetNetAddressCount,
834 (UINT32)addressIdx))
835 goto out;
836
837 ret = addressIdx > 0;
838 }
839 }
840
841out:
842 WINPR_JSON_Delete(json);
843 return ret;
844}
845
846static void zfree(char* str)
847{
848 if (str)
849 {
850 char* cur = str;
851 while (*cur != '\0')
852 *cur++ = '\0';
853 }
854 free(str);
855}
856
857static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const WINPR_JSON* json,
858 const rdpCertificate* redirectedServerCert)
859{
860 WINPR_ASSERT(arm);
861 BOOL ret = FALSE;
862 BYTE* authBlob = NULL;
863 WCHAR* wGUID = NULL;
864
865 const char* redirUser = freerdp_settings_get_string(settings, FreeRDP_RedirectionUsername);
866 if (redirUser)
867 {
868 if (!freerdp_settings_set_string(settings, FreeRDP_Username, redirUser))
869 goto end;
870 }
871
872 /* Azure/Entra requires the domain field to be set to 'AzureAD' in most cases.
873 * Some setups have been reported to require a different one, so only supply the suggested
874 * default if there was no other domain provided.
875 */
876 {
877 const char* redirDomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
878 if (!redirDomain)
879 {
880 if (!freerdp_settings_set_string(settings, FreeRDP_Domain, "AzureAD"))
881 goto end;
882 }
883 }
884
885 {
886 const char* duser = freerdp_settings_get_string(settings, FreeRDP_Username);
887 const char* ddomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
888 const char* dpwd = freerdp_settings_get_string(settings, FreeRDP_Password);
889 if (!duser || !dpwd)
890 {
891 WINPR_ASSERT(arm->context);
892 WINPR_ASSERT(arm->context->instance);
893
894 char* username = NULL;
895 char* password = NULL;
896 char* domain = NULL;
897
898 if (ddomain)
899 domain = _strdup(ddomain);
900 if (duser)
901 username = _strdup(duser);
902
903 const BOOL rc =
904 IFCALLRESULT(FALSE, arm->context->instance->AuthenticateEx, arm->context->instance,
905 &username, &password, &domain, AUTH_RDSTLS);
906
907 const BOOL rc1 = freerdp_settings_set_string(settings, FreeRDP_Username, username);
908 const BOOL rc2 = freerdp_settings_set_string(settings, FreeRDP_Password, password);
909 const BOOL rc3 = freerdp_settings_set_string(settings, FreeRDP_Domain, domain);
910 zfree(username);
911 zfree(password);
912 zfree(domain);
913 if (!rc || !rc1 || !rc2 || !rc3)
914 goto end;
915 }
916 }
917
918 /* redirectedAuthGuid */
919 {
920 WINPR_JSON* redirectedAuthGuidNode =
921 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
922 if (!redirectedAuthGuidNode || !WINPR_JSON_IsString(redirectedAuthGuidNode))
923 goto end;
924
925 {
926 const char* redirectedAuthGuid = WINPR_JSON_GetStringValue(redirectedAuthGuidNode);
927 if (!redirectedAuthGuid)
928 goto end;
929
930 {
931 size_t wGUID_len = 0;
932 wGUID = ConvertUtf8ToWCharAlloc(redirectedAuthGuid, &wGUID_len);
933 if (!wGUID || (wGUID_len == 0))
934 {
935 WLog_Print(arm->log, WLOG_ERROR,
936 "unable to allocate space for redirectedAuthGuid");
937 goto end;
938 }
939
940 {
941 const BOOL status = freerdp_settings_set_pointer_len(
942 settings, FreeRDP_RedirectionGuid, wGUID, (wGUID_len + 1) * sizeof(WCHAR));
943
944 if (!status)
945 {
946 WLog_Print(arm->log, WLOG_ERROR, "unable to set RedirectionGuid");
947 goto end;
948 }
949 }
950 }
951 }
952 }
953
954 /* redirectedAuthBlob */
955 {
956 size_t authBlobLen = 0;
957 if (!arm_pick_base64Utf16Field(arm->log, json, "redirectedAuthBlob", &authBlob,
958 &authBlobLen))
959 goto end;
960
961 {
962 size_t blockSize = 0;
963 WINPR_CIPHER_CTX* cipher = treatAuthBlob(arm->log, authBlob, authBlobLen, &blockSize);
964 if (!cipher)
965 goto end;
966
967 {
968 const BOOL rerp = arm_encodeRedirectPasswd(arm->log, settings, redirectedServerCert,
969 cipher, blockSize);
970 winpr_Cipher_Free(cipher);
971 if (!rerp)
972 goto end;
973 }
974 }
975 }
976
977 ret = TRUE;
978
979end:
980 free(wGUID);
981 free(authBlob);
982 return ret;
983}
984
985static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
986{
987 WINPR_ASSERT(arm);
988 WINPR_ASSERT(arm->context);
989 WINPR_ASSERT(message);
990
991 rdpCertificate* redirectedServerCert = NULL;
992 WINPR_JSON* json = WINPR_JSON_ParseWithLength(message, len);
993 BOOL status = FALSE;
994 if (!json)
995 {
996 WLog_Print(arm->log, WLOG_ERROR, "Response data is not valid JSON: %s",
998 return FALSE;
999 }
1000
1001 if (WLog_IsLevelActive(arm->log, WLOG_DEBUG))
1002 {
1003 char* str = WINPR_JSON_PrintUnformatted(json);
1004 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", str);
1005 free(str);
1006 }
1007
1008 rdpSettings* settings = arm->context->settings;
1009 WINPR_JSON* gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocationPreWebSocket");
1010 if (!gwurl)
1011 gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
1012 const char* gwurlstr = WINPR_JSON_GetStringValue(gwurl);
1013 if (gwurlstr != NULL)
1014 {
1015 WLog_Print(arm->log, WLOG_DEBUG, "extracted target url %s", gwurlstr);
1016 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurlstr))
1017 goto fail;
1018 }
1019
1020 {
1021 WINPR_JSON* serverNameNode =
1022 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedServerName");
1023 if (serverNameNode)
1024 {
1025 const char* serverName = WINPR_JSON_GetStringValue(serverNameNode);
1026 if (serverName)
1027 {
1028 if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName))
1029 goto fail;
1030 }
1031 }
1032 }
1033
1034 {
1035 const char key[] = "redirectedUsername";
1036 if (WINPR_JSON_HasObjectItem(json, key))
1037 {
1038 const char* userName = NULL;
1039 WINPR_JSON* userNameNode = WINPR_JSON_GetObjectItemCaseSensitive(json, key);
1040 if (userNameNode)
1041 userName = WINPR_JSON_GetStringValue(userNameNode);
1042 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionUsername, userName))
1043 goto fail;
1044 }
1045 }
1046
1047 {
1048 WINPR_JSON* azureMeta =
1049 WINPR_JSON_GetObjectItemCaseSensitive(json, "azureInstanceNetworkMetadata");
1050 if (azureMeta && WINPR_JSON_IsString(azureMeta))
1051 {
1052 if (!arm_treat_azureInstanceNetworkMetadata(
1053 arm->log, WINPR_JSON_GetStringValue(azureMeta), settings))
1054 {
1055 WLog_Print(arm->log, WLOG_ERROR,
1056 "error when treating azureInstanceNetworkMetadata");
1057 goto fail;
1058 }
1059 }
1060 }
1061
1062 /* redirectedServerCert */
1063 {
1064 size_t certLen = 0;
1065 BYTE* cert = NULL;
1066 if (arm_pick_base64Utf16Field(arm->log, json, "redirectedServerCert", &cert, &certLen))
1067 {
1068 const BOOL rc = rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen);
1069 free(cert);
1070 if (!rc)
1071 goto fail;
1072 else if (!rdp_set_target_certificate(settings, redirectedServerCert))
1073 goto fail;
1074 }
1075 }
1076
1077 if (freerdp_settings_get_bool(settings, FreeRDP_AadSecurity))
1078 status = TRUE;
1079 else
1080 status = arm_fill_rdstls(arm, settings, json, redirectedServerCert);
1081
1082fail:
1083 WINPR_JSON_Delete(json);
1084 freerdp_certificate_free(redirectedServerCert);
1085 return status;
1086}
1087
1088static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
1089{
1090 const size_t len = http_response_get_body_length(response);
1091 const char* msg = http_response_get_body(response);
1092 const size_t alen = strnlen(msg, len + 1);
1093 if (alen > len)
1094 {
1095 WLog_Print(arm->log, WLOG_ERROR, "Got HTTP Response data with invalid termination");
1096 return FALSE;
1097 }
1098
1099 return arm_fill_gateway_parameters(arm, msg, len);
1100}
1101
1102static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
1103{
1104 WINPR_ASSERT(response);
1105 WINPR_ASSERT(retry);
1106
1107 *retry = FALSE;
1108
1109 BOOL rc = FALSE;
1110
1111 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1112
1113 const size_t len = http_response_get_body_length(response);
1114 const char* msg = http_response_get_body(response);
1115 if (msg && (strnlen(msg, len + 1) > len))
1116 {
1117 WLog_Print(arm->log, WLOG_ERROR, "Got HTTP Response data, but length is invalid");
1118
1119 return FALSE;
1120 }
1121
1122 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
1123
1124 WINPR_JSON* json = WINPR_JSON_ParseWithLength(msg, len);
1125 if (json == NULL)
1126 {
1127 const char* error_ptr = WINPR_JSON_GetErrorPtr();
1128 WLog_Print(arm->log, WLOG_ERROR, "WINPR_JSON_ParseWithLength: %s", error_ptr);
1129
1130 return FALSE;
1131 }
1132 else
1133 {
1134 WINPR_JSON* gateway_code_obj = WINPR_JSON_GetObjectItemCaseSensitive(json, "Code");
1135 const char* gw_code_str = WINPR_JSON_GetStringValue(gateway_code_obj);
1136 if (gw_code_str == NULL)
1137 {
1138 WLog_Print(arm->log, WLOG_ERROR, "Response has no \"Code\" property");
1139 goto fail;
1140 }
1141
1142 if (strcmp(gw_code_str, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
1143 {
1144 *retry = TRUE;
1145 WINPR_JSON* message = WINPR_JSON_GetObjectItemCaseSensitive(json, "Message");
1146 const char* msgstr = WINPR_JSON_GetStringValue(message);
1147 if (!msgstr)
1148 WLog_WARN(TAG, "Starting your VM. It may take up to 5 minutes");
1149 else
1150 WLog_WARN(TAG, "%s", msgstr);
1151 freerdp_set_last_error_if_not(arm->context, FREERDP_ERROR_CONNECT_TARGET_BOOTING);
1152 }
1153 else
1154 {
1155 goto fail;
1156 }
1157 }
1158
1159 rc = TRUE;
1160fail:
1161 WINPR_JSON_Delete(json);
1162 return rc;
1163}
1164
1165static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
1166{
1167 WINPR_ASSERT(retry);
1168
1169 if (!arm_fetch_wellknown(arm))
1170 {
1171 *retry = TRUE;
1172 return FALSE;
1173 }
1174
1175 *retry = FALSE;
1176
1177 char* message = NULL;
1178 BOOL rc = FALSE;
1179
1180 HttpResponse* response = NULL;
1181 long StatusCode = 0;
1182
1183 const char* useragent =
1184 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpUserAgent);
1185 const char* msuseragent =
1186 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpMsUserAgent);
1187 if (!http_context_set_uri(arm->http, "/api/arm/v2/connections") ||
1188 !http_context_set_accept(arm->http, "*/*") ||
1189 !http_context_set_cache_control(arm->http, "no-cache") ||
1190 !http_context_set_pragma(arm->http, "no-cache") ||
1191 !http_context_set_connection(arm->http, "Keep-Alive") ||
1192 !http_context_set_user_agent(arm->http, useragent) ||
1193 !http_context_set_x_ms_user_agent(arm->http, msuseragent) ||
1194 !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
1195 FreeRDP_GatewayHostname)))
1196 goto arm_error;
1197
1198 if (!arm_tls_connect(arm, arm->tls, timeout))
1199 goto arm_error;
1200
1201 message = arm_create_request_json(arm);
1202 if (!message)
1203 goto arm_error;
1204
1205 if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
1206 goto arm_error;
1207
1208 response = http_response_recv(arm->tls, TRUE);
1209 if (!response)
1210 goto arm_error;
1211
1212 StatusCode = http_response_get_status_code(response);
1213 if (StatusCode == HTTP_STATUS_OK)
1214 {
1215 if (!arm_handle_request_ok(arm, response))
1216 goto arm_error;
1217 }
1218 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1219 {
1220 if (!arm_handle_bad_request(arm, response, retry))
1221 goto arm_error;
1222 }
1223 else
1224 {
1225 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1226 goto arm_error;
1227 }
1228
1229 rc = TRUE;
1230arm_error:
1231 http_response_free(response);
1232 free(message);
1233 return rc;
1234}
1235
1236#endif
1237
1238BOOL arm_resolve_endpoint(wLog* log, rdpContext* context, DWORD timeout)
1239{
1240#ifndef WITH_AAD
1241 WLog_Print(log, WLOG_ERROR, "arm gateway support not compiled in");
1242 return FALSE;
1243#else
1244
1245 if (!context)
1246 return FALSE;
1247
1248 if (!context->settings)
1249 return FALSE;
1250
1251 if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
1252 (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
1253 {
1254 WLog_Print(log, WLOG_ERROR, "loadBalanceInfo and RemoteApplicationProgram needed");
1255 return FALSE;
1256 }
1257
1258 rdpArm* arm = arm_new(context);
1259 if (!arm)
1260 return FALSE;
1261
1262 BOOL retry = FALSE;
1263 BOOL rc = FALSE;
1264 do
1265 {
1266 if (retry && rc)
1267 {
1268 freerdp* instance = context->instance;
1269 WINPR_ASSERT(instance);
1270 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance, "arm-transport",
1271 arm->gateway_retry, arm);
1272 arm->gateway_retry++;
1273 if (delay <= 0)
1274 break; /* error or no retry desired, abort loop */
1275 else
1276 {
1277 WLog_Print(arm->log, WLOG_DEBUG, "Delay for %" PRIdz "ms before next attempt",
1278 delay);
1279 while (delay > 0)
1280 {
1281 DWORD slp = (UINT32)delay;
1282 if (delay > UINT32_MAX)
1283 slp = UINT32_MAX;
1284 Sleep(slp);
1285 delay -= slp;
1286 }
1287 }
1288 }
1289 rc = arm_handle_request(arm, &retry, timeout);
1290
1291 } while (retry && rc);
1292 arm_free(arm);
1293 return rc;
1294#endif
1295}
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
Definition c-json.c:132
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition c-json.c:182
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition c-json.c:232
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
Definition c-json.c:108
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
Definition c-json.c:127
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition c-json.c:269
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition c-json.c:98
WINPR_API char * WINPR_JSON_PrintUnformatted(WINPR_JSON *item)
Serialize a JSON instance to string without formatting for human readable formatted output see WINPR_...
Definition c-json.c:301
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition c-json.c:142
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition c-json.c:237
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition c-json.c:103
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition c-json.c:114
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition c-json.c:187
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition c-json.c:137
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition c-json.c:93
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *param)
Sets a string settings value. The param is copied.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API BOOL freerdp_settings_set_pointer_len(rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id, const void *data, size_t len)
Set a pointer to value data.
FREERDP_API WCHAR * freerdp_settings_get_string_as_utf16(const rdpSettings *settings, FreeRDP_Settings_Keys_String id, size_t *pCharLen)
Return an allocated UTF16 string.
FREERDP_API const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.