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 HttpRequest* request = NULL;
190 const char* uri = NULL;
191
192 WINPR_ASSERT(arm);
193 WINPR_ASSERT(method);
194 WINPR_ASSERT(content_type);
195
196 WINPR_ASSERT(arm->context);
197 WINPR_ASSERT(arm->context->rdp);
198
199 uri = http_context_get_uri(arm->http);
200 request = http_request_new();
201
202 if (!request)
203 return NULL;
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(arm->context->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(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer,
229 token))
230 {
231 free(token);
232 goto out;
233 }
234 free(token);
235 }
236
237 if (!http_request_set_auth_scheme(request, "Bearer") ||
238 !http_request_set_auth_param(
239 request,
240 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)))
241 goto out;
242
243 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
244 !http_request_set_content_length(request, content_length) ||
245 !http_request_set_content_type(request, content_type))
246 goto out;
247
248 s = http_request_write(arm->http, request);
249out:
250 http_request_free(request);
251
252 if (s)
253 Stream_SealLength(s);
254
255 return s;
256}
257
258static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls, const char* method,
259 const char* content_type, const char* data, size_t content_length)
260{
261 int status = -1;
262 wStream* s =
263 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
264
265 if (!s)
266 return FALSE;
267
268 const size_t sz = Stream_Length(s);
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 const size_t len =
336 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength);
337 memcpy(lbi, freerdp_settings_get_pointer(arm->context->settings, FreeRDP_LoadBalanceInfo), len);
338
339 WINPR_JSON_AddStringToObject(json, "loadBalanceInfo", lbi);
340 WINPR_JSON_AddNullToObject(json, "LogonToken");
341 WINPR_JSON_AddNullToObject(json, "gatewayLoadBalancerToken");
342
343 message = WINPR_JSON_PrintUnformatted(json);
344arm_create_cleanup:
345 if (json)
346 WINPR_JSON_Delete(json);
347 free(lbi);
348 return message;
349}
350
365static WINPR_CIPHER_CTX* treatAuthBlob(wLog* log, const BYTE* pbInput, size_t cbInput,
366 size_t* pBlockSize)
367{
368 WINPR_CIPHER_CTX* ret = NULL;
369 char algoName[100] = { 0 };
370
371 WINPR_ASSERT(pBlockSize);
372 SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
373 sizeof(algoName) - 1);
374 if (algoSz <= 0)
375 {
376 WLog_Print(log, WLOG_ERROR, "invalid algoName");
377 return NULL;
378 }
379
380 if (strcmp(algoName, "AES") != 0)
381 {
382 WLog_Print(log, WLOG_ERROR, "only AES is supported for now");
383 return NULL;
384 }
385
386 *pBlockSize = WINPR_AES_BLOCK_SIZE;
387 const size_t algoLen = WINPR_ASSERTING_INT_CAST(size_t, (algoSz + 1)) * sizeof(WCHAR);
388 if (cbInput < algoLen)
389 {
390 WLog_Print(log, WLOG_ERROR, "invalid AuthBlob size");
391 return NULL;
392 }
393
394 cbInput -= algoLen;
395
396 /* BCRYPT_KEY_DATA_BLOB_HEADER */
397 wStream staticStream = { 0 };
398 wStream* s = Stream_StaticConstInit(&staticStream, &pbInput[algoLen], cbInput);
399
400 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 12))
401 return NULL;
402
403 const UINT32 dwMagic = Stream_Get_UINT32(s);
404 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
405 {
406 WLog_Print(log, WLOG_ERROR, "unsupported authBlob type");
407 return NULL;
408 }
409
410 const UINT32 dwVersion = Stream_Get_UINT32(s);
411 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
412 {
413 WLog_Print(log, WLOG_ERROR, "unsupported authBlob version %" PRIu32 ", expecting %d",
414 dwVersion, BCRYPT_KEY_DATA_BLOB_VERSION1);
415 return NULL;
416 }
417
418 const UINT32 cbKeyData = Stream_Get_UINT32(s);
419 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, cbKeyData))
420 {
421 WLog_Print(log, WLOG_ERROR, "invalid authBlob size");
422 return NULL;
423 }
424
425 WINPR_CIPHER_TYPE cipherType = 0;
426 switch (cbKeyData)
427 {
428 case 16:
429 cipherType = WINPR_CIPHER_AES_128_CBC;
430 break;
431 case 24:
432 cipherType = WINPR_CIPHER_AES_192_CBC;
433 break;
434 case 32:
435 cipherType = WINPR_CIPHER_AES_256_CBC;
436 break;
437 default:
438 WLog_Print(log, WLOG_ERROR, "invalid authBlob cipher size");
439 return NULL;
440 }
441
442 ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
443 if (!ret)
444 {
445 WLog_Print(log, WLOG_ERROR, "error creating cipher");
446 return NULL;
447 }
448
449 if (!winpr_Cipher_SetPadding(ret, TRUE))
450 {
451 WLog_Print(log, WLOG_ERROR, "unable to enable padding on cipher");
452 winpr_Cipher_Free(ret);
453 return NULL;
454 }
455
456 return ret;
457}
458
459static BOOL arm_stringEncodeW(const BYTE* pin, size_t cbIn, BYTE** ppOut, size_t* pcbOut)
460{
461 *ppOut = NULL;
462 *pcbOut = 0;
463
464 /* encode to base64 with crlf */
465 char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
466 if (!b64encoded)
467 return FALSE;
468
469 /* and then convert to Unicode */
470 size_t outSz = 0;
471 *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
472 free(b64encoded);
473
474 if (!*ppOut)
475 return FALSE;
476
477 *pcbOut = (outSz + 1) * sizeof(WCHAR);
478 return TRUE;
479}
480
481static BOOL arm_encodeRedirectPasswd(wLog* log, rdpSettings* settings, const rdpCertificate* cert,
482 WINPR_CIPHER_CTX* cipher, size_t blockSize)
483{
484 BOOL ret = FALSE;
485 BYTE* output = NULL;
486 BYTE* finalOutput = NULL;
487
488 /* let's prepare the encrypted password, first we do a
489 * cipheredPass = AES(redirectedAuthBlob, toUtf16(passwd))
490 */
491
492 size_t wpasswdLen = 0;
493 WCHAR* wpasswd = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &wpasswdLen);
494 if (!wpasswd)
495 {
496 WLog_Print(log, WLOG_ERROR, "error when converting password to UTF16");
497 return FALSE;
498 }
499
500 const size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
501 BYTE* encryptedPass = calloc(1, wpasswdBytes + blockSize); /* 16: block size of AES (padding) */
502
503 if (!encryptedPass)
504 goto out;
505
506 size_t encryptedPassLen = 0;
507 if (!winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen))
508 goto out;
509
510 if (encryptedPassLen > wpasswdBytes)
511 goto out;
512
513 size_t finalLen = 0;
514 if (!winpr_Cipher_Final(cipher, &encryptedPass[encryptedPassLen], &finalLen))
515 {
516 WLog_Print(log, WLOG_ERROR, "error when ciphering password");
517 goto out;
518 }
519 encryptedPassLen += finalLen;
520
521 /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
522 size_t output_length = 0;
523 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
524 &output_length))
525 {
526 WLog_Print(log, WLOG_ERROR, "unable to encrypt with the server's public key");
527 goto out;
528 }
529
530 size_t finalOutputLen = 0;
531 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
532 {
533 WLog_Print(log, WLOG_ERROR, "unable to base64+utf16 final blob");
534 goto out;
535 }
536
537 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword, finalOutput,
538 finalOutputLen))
539 {
540 WLog_Print(log, WLOG_ERROR, "unable to set the redirection password in settings");
541 goto out;
542 }
543
544 settings->RdstlsSecurity = TRUE;
545 settings->AadSecurity = FALSE;
546 settings->NlaSecurity = FALSE;
547 settings->RdpSecurity = FALSE;
548 settings->TlsSecurity = FALSE;
549 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
550 ret = TRUE;
551out:
552 free(finalOutput);
553 free(output);
554 free(encryptedPass);
555 free(wpasswd);
556 return ret;
557}
558
563static BOOL arm_pick_base64Utf16Field(wLog* log, const WINPR_JSON* json, const char* name,
564 BYTE** poutput, size_t* plen)
565{
566 *poutput = NULL;
567 *plen = 0;
568
569 WINPR_JSON* node = WINPR_JSON_GetObjectItemCaseSensitive(json, name);
570 if (!node || !WINPR_JSON_IsString(node))
571 return TRUE;
572
573 const char* nodeValue = WINPR_JSON_GetStringValue(node);
574 if (!nodeValue)
575 return TRUE;
576
577 BYTE* output1 = NULL;
578 size_t len1 = 0;
579 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
580 if (!output1 || !len1)
581 {
582 WLog_Print(log, WLOG_ERROR, "error when first unbase64 for %s", name);
583 free(output1);
584 return FALSE;
585 }
586
587 size_t len2 = 0;
588 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
589 free(output1);
590 if (!output2 || !len2)
591 {
592 WLog_Print(log, WLOG_ERROR, "error when decode('utf-16') for %s", name);
593 free(output2);
594 return FALSE;
595 }
596
597 BYTE* output = NULL;
598 crypto_base64_decode(output2, len2, &output, plen);
599 free(output2);
600 if (!output || !*plen)
601 {
602 WLog_Print(log, WLOG_ERROR, "error when second unbase64 for %s", name);
603 free(output);
604 return FALSE;
605 }
606
607 *poutput = output;
608 return TRUE;
609}
610
631static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
632{
633 WINPR_ASSERT(ipvX);
634 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipvX, "ipAddress");
635 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
636 return 0;
637
638 /* Each entry might contain a public and a private address */
639 return WINPR_JSON_GetArraySize(ipAddress) * 2;
640}
641
642static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAddressIdx)
643{
644 WINPR_ASSERT(settings);
645 WINPR_ASSERT(ipv6);
646 WINPR_ASSERT(pAddressIdx);
647
648 if (!freerdp_settings_get_bool(settings, FreeRDP_IPv6Enabled))
649 return TRUE;
650
651 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv6, "ipAddress");
652 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
653 return TRUE;
654 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
655 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
656 for (size_t j = 0; j < naddresses; j++)
657 {
658 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
659 if (!adressN || !WINPR_JSON_IsString(adressN))
660 continue;
661
662 const char* addr = WINPR_JSON_GetStringValue(adressN);
663 if (utils_str_is_empty(addr))
664 continue;
665
666 if (*pAddressIdx >= count)
667 {
668 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
669 return FALSE;
670 }
671
672 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
673 (*pAddressIdx)++, addr))
674 return FALSE;
675 }
676 return TRUE;
677}
678
679static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAddressIdx)
680{
681 WINPR_ASSERT(settings);
682 WINPR_ASSERT(ipv4);
683 WINPR_ASSERT(pAddressIdx);
684
685 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv4, "ipAddress");
686 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
687 return TRUE;
688
689 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
690 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
691 for (size_t j = 0; j < naddresses; j++)
692 {
693 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
694 if (!adressN)
695 continue;
696
697 WINPR_JSON* publicIpNode =
698 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "publicIpAddress");
699 if (publicIpNode && WINPR_JSON_IsString(publicIpNode))
700 {
701 const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode);
702 if (!utils_str_is_empty(publicIp))
703 {
704 if (*pAddressIdx >= count)
705 {
706 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
707 return FALSE;
708 }
709 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
710 (*pAddressIdx)++, publicIp))
711 return FALSE;
712 }
713 }
714
715 WINPR_JSON* privateIpNode =
716 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "privateIpAddress");
717 if (privateIpNode && WINPR_JSON_IsString(privateIpNode))
718 {
719 const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode);
720 if (!utils_str_is_empty(privateIp))
721 {
722 if (*pAddressIdx >= count)
723 {
724 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
725 return FALSE;
726 }
727 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
728 (*pAddressIdx)++, privateIp))
729 return FALSE;
730 }
731 }
732 }
733 return TRUE;
734}
735
736static BOOL arm_treat_azureInstanceNetworkMetadata(wLog* log, const char* metadata,
737 rdpSettings* settings)
738{
739 BOOL ret = FALSE;
740
741 WINPR_ASSERT(settings);
742
743 if (!freerdp_target_net_adresses_reset(settings, 0))
744 return FALSE;
745
746 WINPR_JSON* json = WINPR_JSON_Parse(metadata);
747 if (!json)
748 {
749 WLog_Print(log, WLOG_ERROR, "invalid azureInstanceNetworkMetadata");
750 return FALSE;
751 }
752
753 WINPR_JSON* iface = WINPR_JSON_GetObjectItemCaseSensitive(json, "interface");
754 if (!iface)
755 {
756 ret = TRUE;
757 goto out;
758 }
759
760 if (!WINPR_JSON_IsArray(iface))
761 {
762 WLog_Print(log, WLOG_ERROR, "expecting interface to be an Array");
763 goto out;
764 }
765
766 size_t interfaceSz = WINPR_JSON_GetArraySize(iface);
767 if (interfaceSz == 0)
768 {
769 WLog_WARN(TAG, "no addresses in azure instance metadata");
770 ret = TRUE;
771 goto out;
772 }
773
774 size_t count = 0;
775 for (size_t i = 0; i < interfaceSz; i++)
776 {
777 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
778 if (!interN)
779 continue;
780
781 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
782 if (ipv6)
783 count += arm_parse_ipvx_count(ipv6);
784
785 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
786 if (ipv4)
787 count += arm_parse_ipvx_count(ipv4);
788 }
789
790 if (!freerdp_target_net_adresses_reset(settings, count))
791 return FALSE;
792
793 size_t addressIdx = 0;
794 for (size_t i = 0; i < interfaceSz; i++)
795 {
796 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
797 if (!interN)
798 continue;
799
800 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
801 if (ipv6)
802 {
803 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
804 goto out;
805 }
806
807 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
808 if (ipv4)
809 {
810 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
811 goto out;
812 }
813 }
814 if (addressIdx > UINT32_MAX)
815 goto out;
816
817 if (!freerdp_settings_set_uint32(settings, FreeRDP_TargetNetAddressCount, (UINT32)addressIdx))
818 goto out;
819
820 ret = addressIdx > 0;
821
822out:
823 WINPR_JSON_Delete(json);
824 return ret;
825}
826
827static void zfree(char* str)
828{
829 if (str)
830 {
831 char* cur = str;
832 while (*cur != '\0')
833 *cur++ = '\0';
834 }
835 free(str);
836}
837
838static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const WINPR_JSON* json,
839 const rdpCertificate* redirectedServerCert)
840{
841 WINPR_ASSERT(arm);
842 BOOL ret = FALSE;
843 BYTE* authBlob = NULL;
844 WCHAR* wGUID = NULL;
845
846 const char* redirUser = freerdp_settings_get_string(settings, FreeRDP_RedirectionUsername);
847 if (redirUser)
848 {
849 if (!freerdp_settings_set_string(settings, FreeRDP_Username, redirUser))
850 goto end;
851 }
852
853 /* Azure/Entra requires the domain field to be set to 'AzureAD' in most cases.
854 * Some setups have been reported to require a different one, so only supply the suggested
855 * default if there was no other domain provided.
856 */
857 const char* redirDomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
858 if (!redirDomain)
859 {
860 if (!freerdp_settings_set_string(settings, FreeRDP_Domain, "AzureAD"))
861 goto end;
862 }
863
864 const char* duser = freerdp_settings_get_string(settings, FreeRDP_Username);
865 const char* ddomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
866 const char* dpwd = freerdp_settings_get_string(settings, FreeRDP_Password);
867 if (!duser || !dpwd)
868 {
869 WINPR_ASSERT(arm->context);
870 WINPR_ASSERT(arm->context->instance);
871
872 char* username = NULL;
873 char* password = NULL;
874 char* domain = NULL;
875
876 if (ddomain)
877 domain = _strdup(ddomain);
878 if (duser)
879 username = _strdup(duser);
880
881 const BOOL rc =
882 IFCALLRESULT(FALSE, arm->context->instance->AuthenticateEx, arm->context->instance,
883 &username, &password, &domain, AUTH_RDSTLS);
884
885 const BOOL rc1 = freerdp_settings_set_string(settings, FreeRDP_Username, username);
886 const BOOL rc2 = freerdp_settings_set_string(settings, FreeRDP_Password, password);
887 const BOOL rc3 = freerdp_settings_set_string(settings, FreeRDP_Domain, domain);
888 zfree(username);
889 zfree(password);
890 zfree(domain);
891 if (!rc || !rc1 || !rc2 || !rc3)
892 goto end;
893 }
894
895 /* redirectedAuthGuid */
896 WINPR_JSON* redirectedAuthGuidNode =
897 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
898 if (!redirectedAuthGuidNode || !WINPR_JSON_IsString(redirectedAuthGuidNode))
899 goto end;
900
901 const char* redirectedAuthGuid = WINPR_JSON_GetStringValue(redirectedAuthGuidNode);
902 if (!redirectedAuthGuid)
903 goto end;
904
905 size_t wGUID_len = 0;
906 wGUID = ConvertUtf8ToWCharAlloc(redirectedAuthGuid, &wGUID_len);
907 if (!wGUID || (wGUID_len == 0))
908 {
909 WLog_Print(arm->log, WLOG_ERROR, "unable to allocate space for redirectedAuthGuid");
910 goto end;
911 }
912
913 const BOOL status = freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid, wGUID,
914 (wGUID_len + 1) * sizeof(WCHAR));
915
916 if (!status)
917 {
918 WLog_Print(arm->log, WLOG_ERROR, "unable to set RedirectionGuid");
919 goto end;
920 }
921
922 /* redirectedAuthBlob */
923 size_t authBlobLen = 0;
924 if (!arm_pick_base64Utf16Field(arm->log, json, "redirectedAuthBlob", &authBlob, &authBlobLen))
925 goto end;
926
927 size_t blockSize = 0;
928 WINPR_CIPHER_CTX* cipher = treatAuthBlob(arm->log, authBlob, authBlobLen, &blockSize);
929 if (!cipher)
930 goto end;
931
932 const BOOL rerp =
933 arm_encodeRedirectPasswd(arm->log, settings, redirectedServerCert, cipher, blockSize);
934 winpr_Cipher_Free(cipher);
935 if (!rerp)
936 goto end;
937
938 ret = TRUE;
939
940end:
941 free(wGUID);
942 free(authBlob);
943 return ret;
944}
945
946static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
947{
948 WINPR_ASSERT(arm);
949 WINPR_ASSERT(arm->context);
950 WINPR_ASSERT(message);
951
952 rdpCertificate* redirectedServerCert = NULL;
953 WINPR_JSON* json = WINPR_JSON_ParseWithLength(message, len);
954 BOOL status = FALSE;
955 if (!json)
956 return FALSE;
957
958 rdpSettings* settings = arm->context->settings;
959 WINPR_JSON* gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
960 const char* gwurlstr = WINPR_JSON_GetStringValue(gwurl);
961 if (gwurlstr != NULL)
962 {
963 WLog_Print(arm->log, WLOG_DEBUG, "extracted target url %s", gwurlstr);
964 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurlstr))
965 goto fail;
966 }
967
968 WINPR_JSON* serverNameNode =
969 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedServerName");
970 if (serverNameNode)
971 {
972 const char* serverName = WINPR_JSON_GetStringValue(serverNameNode);
973 if (serverName)
974 {
975 if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName))
976 goto fail;
977 }
978 }
979
980 {
981 const char key[] = "redirectedUsername";
982 if (WINPR_JSON_HasObjectItem(json, key))
983 {
984 const char* userName = NULL;
985 WINPR_JSON* userNameNode = WINPR_JSON_GetObjectItemCaseSensitive(json, key);
986 if (userNameNode)
987 userName = WINPR_JSON_GetStringValue(userNameNode);
988 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionUsername, userName))
989 goto fail;
990 }
991 }
992
993 WINPR_JSON* azureMeta =
994 WINPR_JSON_GetObjectItemCaseSensitive(json, "azureInstanceNetworkMetadata");
995 if (azureMeta && WINPR_JSON_IsString(azureMeta))
996 {
997 if (!arm_treat_azureInstanceNetworkMetadata(arm->log, WINPR_JSON_GetStringValue(azureMeta),
998 settings))
999 {
1000 WLog_Print(arm->log, WLOG_ERROR, "error when treating azureInstanceNetworkMetadata");
1001 goto fail;
1002 }
1003 }
1004
1005 /* redirectedServerCert */
1006 size_t certLen = 0;
1007 BYTE* cert = NULL;
1008 if (arm_pick_base64Utf16Field(arm->log, json, "redirectedServerCert", &cert, &certLen))
1009 {
1010 const BOOL rc = rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen);
1011 free(cert);
1012 if (!rc)
1013 goto fail;
1014 else if (!rdp_set_target_certificate(settings, redirectedServerCert))
1015 goto fail;
1016 }
1017
1018 if (freerdp_settings_get_bool(settings, FreeRDP_AadSecurity))
1019 status = TRUE;
1020 else
1021 status = arm_fill_rdstls(arm, settings, json, redirectedServerCert);
1022
1023fail:
1024 WINPR_JSON_Delete(json);
1025 freerdp_certificate_free(redirectedServerCert);
1026 return status;
1027}
1028
1029static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
1030{
1031 const size_t len = http_response_get_body_length(response);
1032 const char* msg = (const char*)http_response_get_body(response);
1033 if (strnlen(msg, len + 1) > len)
1034 return FALSE;
1035
1036 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
1037 return arm_fill_gateway_parameters(arm, msg, len);
1038}
1039
1040static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
1041{
1042 WINPR_ASSERT(response);
1043 WINPR_ASSERT(retry);
1044
1045 *retry = FALSE;
1046
1047 BOOL rc = FALSE;
1048
1049 const size_t len = http_response_get_body_length(response);
1050 const char* msg = (const char*)http_response_get_body(response);
1051 if (strnlen(msg, len + 1) > len)
1052 return FALSE;
1053
1054 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
1055
1056 WINPR_JSON* json = WINPR_JSON_ParseWithLength(msg, len);
1057 if (json == NULL)
1058 {
1059 const char* error_ptr = WINPR_JSON_GetErrorPtr();
1060 if (error_ptr != NULL)
1061 WLog_Print(arm->log, WLOG_ERROR, "NullPoException: %s", error_ptr);
1062
1063 return FALSE;
1064 }
1065
1066 WINPR_JSON* gateway_code_obj = WINPR_JSON_GetObjectItemCaseSensitive(json, "Code");
1067 const char* gw_code_str = WINPR_JSON_GetStringValue(gateway_code_obj);
1068 if (gw_code_str == NULL)
1069 {
1070 WLog_Print(arm->log, WLOG_ERROR, "Response has no \"Code\" property");
1071 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1072 goto fail;
1073 }
1074
1075 if (strcmp(gw_code_str, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
1076 {
1077 *retry = TRUE;
1078 WINPR_JSON* message = WINPR_JSON_GetObjectItemCaseSensitive(json, "Message");
1079 const char* msgstr = WINPR_JSON_GetStringValue(message);
1080 if (!msgstr)
1081 WLog_WARN(TAG, "Starting your VM. It may take up to 5 minutes");
1082 else
1083 WLog_WARN(TAG, "%s", msgstr);
1084 freerdp_set_last_error_if_not(arm->context, FREERDP_ERROR_CONNECT_TARGET_BOOTING);
1085 }
1086 else
1087 {
1088 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1089 goto fail;
1090 }
1091
1092 rc = TRUE;
1093fail:
1094 WINPR_JSON_Delete(json);
1095 return rc;
1096}
1097
1098static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
1099{
1100 WINPR_ASSERT(retry);
1101
1102 if (!arm_fetch_wellknown(arm))
1103 {
1104 *retry = TRUE;
1105 return FALSE;
1106 }
1107
1108 *retry = FALSE;
1109
1110 char* message = NULL;
1111 BOOL rc = FALSE;
1112
1113 HttpResponse* response = NULL;
1114 long StatusCode = 0;
1115
1116 if (!http_context_set_uri(arm->http, "/api/arm/v2/connections/") ||
1117 !http_context_set_accept(arm->http, "application/json") ||
1118 !http_context_set_cache_control(arm->http, "no-cache") ||
1119 !http_context_set_pragma(arm->http, "no-cache") ||
1120 !http_context_set_connection(arm->http, "Keep-Alive") ||
1121 !http_context_set_user_agent(arm->http, FREERDP_USER_AGENT) ||
1122 !http_context_set_x_ms_user_agent(arm->http, FREERDP_USER_AGENT) ||
1123 !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
1124 FreeRDP_GatewayHostname)))
1125 goto arm_error;
1126
1127 if (!arm_tls_connect(arm, arm->tls, timeout))
1128 goto arm_error;
1129
1130 message = arm_create_request_json(arm);
1131 if (!message)
1132 goto arm_error;
1133
1134 if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
1135 goto arm_error;
1136
1137 response = http_response_recv(arm->tls, TRUE);
1138 if (!response)
1139 goto arm_error;
1140
1141 StatusCode = http_response_get_status_code(response);
1142 if (StatusCode == HTTP_STATUS_OK)
1143 {
1144 if (!arm_handle_request_ok(arm, response))
1145 goto arm_error;
1146 }
1147 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1148 {
1149 if (!arm_handle_bad_request(arm, response, retry))
1150 goto arm_error;
1151 }
1152 else
1153 {
1154 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1155 goto arm_error;
1156 }
1157
1158 rc = TRUE;
1159arm_error:
1160 http_response_free(response);
1161 free(message);
1162 return rc;
1163}
1164
1165#endif
1166
1167BOOL arm_resolve_endpoint(wLog* log, rdpContext* context, DWORD timeout)
1168{
1169#ifndef WITH_AAD
1170 WLog_Print(log, WLOG_ERROR, "arm gateway support not compiled in");
1171 return FALSE;
1172#else
1173
1174 if (!context)
1175 return FALSE;
1176
1177 if (!context->settings)
1178 return FALSE;
1179
1180 if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
1181 (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
1182 {
1183 WLog_Print(log, WLOG_ERROR, "loadBalanceInfo and RemoteApplicationProgram needed");
1184 return FALSE;
1185 }
1186
1187 rdpArm* arm = arm_new(context);
1188 if (!arm)
1189 return FALSE;
1190
1191 BOOL retry = FALSE;
1192 BOOL rc = FALSE;
1193 do
1194 {
1195 if (retry && rc)
1196 {
1197 freerdp* instance = context->instance;
1198 WINPR_ASSERT(instance);
1199 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance, "arm-transport",
1200 arm->gateway_retry, arm);
1201 arm->gateway_retry++;
1202 if (delay <= 0)
1203 break; /* error or no retry desired, abort loop */
1204 else
1205 {
1206 WLog_Print(arm->log, WLOG_DEBUG, "Delay for %" PRIdz "ms before next attempt",
1207 delay);
1208 while (delay > 0)
1209 {
1210 DWORD slp = (UINT32)delay;
1211 if (delay > UINT32_MAX)
1212 slp = UINT32_MAX;
1213 Sleep(slp);
1214 delay -= slp;
1215 }
1216 }
1217 }
1218 rc = arm_handle_request(arm, &retry, timeout);
1219
1220 } while (retry && rc);
1221 arm_free(arm);
1222 return rc;
1223#endif
1224}
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:262
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:294
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.