FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
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
198 freerdp* instance = arm->context->instance;
199 WINPR_ASSERT(instance);
200
201 uri = http_context_get_uri(arm->http);
202 request = http_request_new();
203
204 if (!request)
205 return NULL;
206
207 if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
208 goto out;
209
210 if (!freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
211 {
212 char* token = NULL;
213
214 if (!instance->GetAccessToken)
215 {
216 WLog_Print(arm->log, WLOG_ERROR, "No authorization token provided");
217 goto out;
218 }
219
220 if (!arm_fetch_wellknown(arm))
221 goto out;
222
223 if (!instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AVD, &token, 0))
224 {
225 WLog_Print(arm->log, WLOG_ERROR, "Unable to obtain access token");
226 goto out;
227 }
228
229 if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer,
230 token))
231 {
232 free(token);
233 goto out;
234 }
235 free(token);
236 }
237
238 if (!http_request_set_auth_scheme(request, "Bearer") ||
239 !http_request_set_auth_param(
240 request,
241 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)))
242 goto out;
243
244 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
245 !http_request_set_content_length(request, content_length) ||
246 !http_request_set_content_type(request, content_type))
247 goto out;
248
249 s = http_request_write(arm->http, request);
250out:
251 http_request_free(request);
252
253 if (s)
254 Stream_SealLength(s);
255
256 return s;
257}
258
259static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls, const char* method,
260 const char* content_type, const char* data, size_t content_length)
261{
262 int status = -1;
263 wStream* s =
264 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
265
266 if (!s)
267 return FALSE;
268
269 const size_t sz = Stream_Length(s);
270 status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
271
272 Stream_Free(s, TRUE);
273 if (status >= 0 && content_length > 0 && data)
274 status = freerdp_tls_write_all(tls, (const BYTE*)data, content_length);
275
276 return (status >= 0);
277}
278
279static void arm_free(rdpArm* arm)
280{
281 if (!arm)
282 return;
283
284 freerdp_tls_free(arm->tls);
285 http_context_free(arm->http);
286
287 free(arm);
288}
289
290static rdpArm* arm_new(rdpContext* context)
291{
292 WINPR_ASSERT(context);
293
294 rdpArm* arm = (rdpArm*)calloc(1, sizeof(rdpArm));
295 if (!arm)
296 goto fail;
297
298 arm->log = WLog_Get(TAG);
299 arm->context = context;
300 arm->tls = freerdp_tls_new(context);
301 if (!arm->tls)
302 goto fail;
303
304 arm->http = http_context_new();
305
306 if (!arm->http)
307 goto fail;
308
309 return arm;
310
311fail:
312 arm_free(arm);
313 return NULL;
314}
315
316static char* arm_create_request_json(rdpArm* arm)
317{
318 char* lbi = NULL;
319 char* message = NULL;
320
321 WINPR_ASSERT(arm);
322
323 WINPR_JSON* json = WINPR_JSON_CreateObject();
324 if (!json)
325 goto arm_create_cleanup;
327 json, "application",
328 freerdp_settings_get_string(arm->context->settings, FreeRDP_RemoteApplicationProgram));
329
330 lbi = calloc(
331 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength) + 1,
332 sizeof(char));
333 if (!lbi)
334 goto arm_create_cleanup;
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), len);
339
340 WINPR_JSON_AddStringToObject(json, "loadBalanceInfo", lbi);
341 WINPR_JSON_AddNullToObject(json, "LogonToken");
342 WINPR_JSON_AddNullToObject(json, "gatewayLoadBalancerToken");
343
344 message = WINPR_JSON_PrintUnformatted(json);
345arm_create_cleanup:
346 if (json)
347 WINPR_JSON_Delete(json);
348 free(lbi);
349 return message;
350}
351
366static WINPR_CIPHER_CTX* treatAuthBlob(wLog* log, const BYTE* pbInput, size_t cbInput)
367{
368 WINPR_CIPHER_CTX* ret = NULL;
369 char algoName[100] = { 0 };
370
371 SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
372 sizeof(algoName));
373 if (algoSz <= 0)
374 {
375 WLog_Print(log, WLOG_ERROR, "invalid algoName");
376 return NULL;
377 }
378
379 algoName[algoSz] = 0;
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 cbInput -= WINPR_ASSERTING_INT_CAST(size_t, (algoSz + 1)) * sizeof(WCHAR);
387
388 if (cbInput < 12)
389 {
390 WLog_Print(log, WLOG_ERROR, "invalid AuthBlob size");
391 return NULL;
392 }
393
394 /* BCRYPT_KEY_DATA_BLOB_HEADER */
395 wStream staticStream = { 0 };
396 wStream* s = Stream_StaticConstInit(
397 &staticStream, pbInput + WINPR_ASSERTING_INT_CAST(size_t, (algoSz + 1)) * sizeof(WCHAR),
398 cbInput);
399
400 UINT32 dwMagic = 0;
401 Stream_Read_UINT32(s, dwMagic);
402
403 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
404 {
405 WLog_Print(log, WLOG_ERROR, "unsupported authBlob type");
406 return NULL;
407 }
408
409 UINT32 dwVersion = 0;
410 Stream_Read_UINT32(s, dwVersion);
411 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
412 {
413 WLog_Print(log, WLOG_ERROR, "unsupported authBlob version %d, expecting %d", dwVersion,
414 BCRYPT_KEY_DATA_BLOB_VERSION1);
415 return NULL;
416 }
417
418 UINT32 cbKeyData = 0;
419 Stream_Read_UINT32(s, cbKeyData);
420 cbInput -= 12;
421
422 if (cbKeyData > cbInput)
423 {
424 WLog_Print(log, WLOG_ERROR, "invalid authBlob size");
425 return NULL;
426 }
427
428 WINPR_CIPHER_TYPE cipherType = 0;
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)
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 size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
504 BYTE* encryptedPass = calloc(1, wpasswdBytes + 16); /* 16: block size of AES (padding) */
505 size_t encryptedPassLen = 0;
506 size_t finalLen = 0;
507 if (!encryptedPass ||
508 !winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen) ||
509 !winpr_Cipher_Final(cipher, encryptedPass + encryptedPassLen, &finalLen))
510 {
511 WLog_Print(log, WLOG_ERROR, "error when ciphering password");
512 goto out;
513 }
514 encryptedPassLen += finalLen;
515
516 /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
517 size_t output_length = 0;
518
519 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
520 &output_length))
521 {
522 WLog_Print(log, WLOG_ERROR, "unable to encrypt with the server's public key");
523 goto out;
524 }
525
526 size_t finalOutputLen = 0;
527 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
528 {
529 WLog_Print(log, WLOG_ERROR, "unable to base64+utf16 final blob");
530 goto out;
531 }
532
533 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword, finalOutput,
534 finalOutputLen))
535 {
536 WLog_Print(log, WLOG_ERROR, "unable to set the redirection password in settings");
537 goto out;
538 }
539
540 settings->RdstlsSecurity = TRUE;
541 settings->AadSecurity = FALSE;
542 settings->NlaSecurity = FALSE;
543 settings->RdpSecurity = FALSE;
544 settings->TlsSecurity = FALSE;
545 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
546 ret = TRUE;
547out:
548 free(finalOutput);
549 free(output);
550 free(encryptedPass);
551 free(wpasswd);
552 return ret;
553}
554
559static BOOL arm_pick_base64Utf16Field(wLog* log, const WINPR_JSON* json, const char* name,
560 BYTE** poutput, size_t* plen)
561{
562 *poutput = NULL;
563 *plen = 0;
564
565 WINPR_JSON* node = WINPR_JSON_GetObjectItemCaseSensitive(json, name);
566 if (!node || !WINPR_JSON_IsString(node))
567 return TRUE;
568
569 const char* nodeValue = WINPR_JSON_GetStringValue(node);
570 if (!nodeValue)
571 return TRUE;
572
573 BYTE* output1 = NULL;
574 size_t len1 = 0;
575 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
576 if (!output1 || !len1)
577 {
578 WLog_Print(log, WLOG_ERROR, "error when first unbase64 for %s", name);
579 free(output1);
580 return FALSE;
581 }
582
583 size_t len2 = 0;
584 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
585 free(output1);
586 if (!output2 || !len2)
587 {
588 WLog_Print(log, WLOG_ERROR, "error when decode('utf-16') for %s", name);
589 free(output2);
590 return FALSE;
591 }
592
593 BYTE* output = NULL;
594 crypto_base64_decode(output2, len2, &output, plen);
595 free(output2);
596 if (!output || !*plen)
597 {
598 WLog_Print(log, WLOG_ERROR, "error when second unbase64 for %s", name);
599 free(output);
600 return FALSE;
601 }
602
603 *poutput = output;
604 return TRUE;
605}
606
627static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
628{
629 WINPR_ASSERT(ipvX);
630 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipvX, "ipAddress");
631 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
632 return 0;
633 return WINPR_JSON_GetArraySize(ipAddress);
634}
635
636static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAddressIdx)
637{
638 WINPR_ASSERT(settings);
639 WINPR_ASSERT(ipv6);
640 WINPR_ASSERT(pAddressIdx);
641
642 if (!freerdp_settings_get_bool(settings, FreeRDP_IPv6Enabled))
643 return TRUE;
644
645 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipv6, "ipAddress");
646 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
647 return TRUE;
648 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
649 for (size_t j = 0; j < naddresses; j++)
650 {
651 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
652 if (!adressN || !WINPR_JSON_IsString(adressN))
653 continue;
654
655 const char* addr = WINPR_JSON_GetStringValue(adressN);
656 if (utils_str_is_empty(addr))
657 continue;
658 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
659 (*pAddressIdx)++, addr))
660 return FALSE;
661 }
662 return TRUE;
663}
664
665static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAddressIdx)
666{
667 WINPR_ASSERT(settings);
668 WINPR_ASSERT(ipv4);
669 WINPR_ASSERT(pAddressIdx);
670
671 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipv4, "ipAddress");
672 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
673 return TRUE;
674
675 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
676 for (size_t j = 0; j < naddresses; j++)
677 {
678 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
679 if (!adressN)
680 continue;
681
682 WINPR_JSON* publicIpNode = WINPR_JSON_GetObjectItem(adressN, "publicIpAddress");
683 if (publicIpNode && WINPR_JSON_IsString(publicIpNode))
684 {
685 const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode);
686 if (!utils_str_is_empty(publicIp))
687 {
688 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
689 (*pAddressIdx)++, publicIp))
690 return FALSE;
691 }
692 }
693
694 WINPR_JSON* privateIpNode = WINPR_JSON_GetObjectItem(adressN, "privateIpAddress");
695 if (privateIpNode && WINPR_JSON_IsString(privateIpNode))
696 {
697 const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode);
698 if (!utils_str_is_empty(privateIp))
699 {
700 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
701 (*pAddressIdx)++, privateIp))
702 return FALSE;
703 }
704 }
705 }
706 return TRUE;
707}
708
709static BOOL arm_treat_azureInstanceNetworkMetadata(wLog* log, const char* metadata,
710 rdpSettings* settings)
711{
712 BOOL ret = FALSE;
713
714 WINPR_ASSERT(settings);
715
716 if (!freerdp_target_net_adresses_reset(settings, 0))
717 return FALSE;
718
719 WINPR_JSON* json = WINPR_JSON_Parse(metadata);
720 if (!json)
721 {
722 WLog_Print(log, WLOG_ERROR, "invalid azureInstanceNetworkMetadata");
723 return FALSE;
724 }
725
726 WINPR_JSON* iface = WINPR_JSON_GetObjectItem(json, "interface");
727 if (!iface)
728 {
729 ret = TRUE;
730 goto out;
731 }
732
733 if (!WINPR_JSON_IsArray(iface))
734 {
735 WLog_Print(log, WLOG_ERROR, "expecting interface to be an Array");
736 goto out;
737 }
738
739 size_t interfaceSz = WINPR_JSON_GetArraySize(iface);
740 if (interfaceSz == 0)
741 {
742 WLog_WARN(TAG, "no addresses in azure instance metadata");
743 ret = TRUE;
744 goto out;
745 }
746
747 size_t count = 0;
748 for (size_t i = 0; i < interfaceSz; i++)
749 {
750 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
751 if (!interN)
752 continue;
753
754 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItem(interN, "ipv6");
755 if (ipv6)
756 count += arm_parse_ipvx_count(ipv6);
757
758 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItem(interN, "ipv4");
759 if (ipv4)
760 count += arm_parse_ipvx_count(ipv4);
761 }
762
763 if (!freerdp_target_net_adresses_reset(settings, count))
764 return FALSE;
765
766 size_t addressIdx = 0;
767 for (size_t i = 0; i < interfaceSz; i++)
768 {
769 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
770 if (!interN)
771 continue;
772
773 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItem(interN, "ipv6");
774 if (ipv6)
775 {
776 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
777 goto out;
778 }
779
780 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItem(interN, "ipv4");
781 if (ipv4)
782 {
783 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
784 goto out;
785 }
786 }
787 if (addressIdx > UINT32_MAX)
788 goto out;
789
790 if (!freerdp_settings_set_uint32(settings, FreeRDP_TargetNetAddressCount, (UINT32)addressIdx))
791 goto out;
792
793 ret = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount) > 0;
794
795out:
796 WINPR_JSON_Delete(json);
797 return ret;
798}
799
800static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const WINPR_JSON* json)
801{
802 BOOL ret = TRUE;
803 BYTE* cert = NULL;
804 BYTE* authBlob = NULL;
805 rdpCertificate* redirectedServerCert = NULL;
806
807 do
808 {
809 /* redirectedAuthGuid */
810 WINPR_JSON* redirectedAuthGuidNode =
811 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
812 if (!redirectedAuthGuidNode || !WINPR_JSON_IsString(redirectedAuthGuidNode))
813 break;
814
815 const char* redirectedAuthGuid = WINPR_JSON_GetStringValue(redirectedAuthGuidNode);
816 if (!redirectedAuthGuid)
817 break;
818
819 WCHAR wGUID[72] = {
820 0
821 }; /* A GUID string is between 32 and 68 characters as string, depending on representation.
822 Add a few extra bytes for braces et al */
823 const SSIZE_T wGUID_len = ConvertUtf8ToWChar(redirectedAuthGuid, wGUID, ARRAYSIZE(wGUID));
824 if (wGUID_len < 0)
825 {
826 WLog_Print(arm->log, WLOG_ERROR, "unable to allocate space for redirectedAuthGuid");
827 ret = FALSE;
828 goto endOfFunction;
829 }
830
832 settings, FreeRDP_RedirectionGuid, wGUID,
833 WINPR_ASSERTING_INT_CAST(size_t, (wGUID_len + 1)) * sizeof(WCHAR));
834 if (!status)
835 {
836 WLog_Print(arm->log, WLOG_ERROR, "unable to set RedirectionGuid");
837 ret = FALSE;
838 goto endOfFunction;
839 }
840
841 /* redirectedServerCert */
842 size_t certLen = 0;
843 if (!arm_pick_base64Utf16Field(arm->log, json, "redirectedServerCert", &cert, &certLen))
844 break;
845
846 if (!rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen))
847 break;
848
849 /* redirectedAuthBlob */
850 size_t authBlobLen = 0;
851 if (!arm_pick_base64Utf16Field(arm->log, json, "redirectedAuthBlob", &authBlob,
852 &authBlobLen))
853 break;
854
855 WINPR_CIPHER_CTX* cipher = treatAuthBlob(arm->log, authBlob, authBlobLen);
856 if (!cipher)
857 break;
858
859 const BOOL rerp =
860 arm_encodeRedirectPasswd(arm->log, settings, redirectedServerCert, cipher);
861 winpr_Cipher_Free(cipher);
862 if (!rerp)
863 break;
864
865 ret = TRUE;
866 } while (FALSE);
867
868 free(cert);
869 freerdp_certificate_free(redirectedServerCert);
870 free(authBlob);
871
872endOfFunction:
873 return ret;
874}
875
876static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
877{
878 WINPR_ASSERT(arm);
879 WINPR_ASSERT(arm->context);
880 WINPR_ASSERT(message);
881
882 WINPR_JSON* json = WINPR_JSON_ParseWithLength(message, len);
883 BOOL status = FALSE;
884 if (!json)
885 return FALSE;
886
887 rdpSettings* settings = arm->context->settings;
888 WINPR_JSON* gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
889 const char* gwurlstr = WINPR_JSON_GetStringValue(gwurl);
890 if (gwurlstr != NULL)
891 {
892 WLog_Print(arm->log, WLOG_DEBUG, "extracted target url %s", gwurlstr);
893 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurlstr))
894 status = FALSE;
895 else
896 status = TRUE;
897 }
898
899 WINPR_JSON* serverNameNode = WINPR_JSON_GetObjectItem(json, "redirectedServerName");
900 if (serverNameNode)
901 {
902 const char* serverName = WINPR_JSON_GetStringValue(serverNameNode);
903 if (serverName)
904 status = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName);
905 }
906
907 WINPR_JSON* azureMeta = WINPR_JSON_GetObjectItem(json, "azureInstanceNetworkMetadata");
908 if (azureMeta && WINPR_JSON_IsString(azureMeta))
909 {
910 if (!arm_treat_azureInstanceNetworkMetadata(arm->log, WINPR_JSON_GetStringValue(azureMeta),
911 settings))
912 {
913 WLog_Print(arm->log, WLOG_ERROR, "error when treating azureInstanceNetworkMetadata");
914 }
915 }
916
917 if (freerdp_settings_get_string(settings, FreeRDP_Password))
918 {
919 /* note: we retrieve some more fields for RDSTLS only if we have a password provided by the
920 * user, otherwise these are useless: we will not be able to do RDSTLS
921 */
922 status = arm_fill_rdstls(arm, settings, json);
923 }
924
925 WINPR_JSON_Delete(json);
926 return status;
927}
928
929static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
930{
931 const size_t len = http_response_get_body_length(response);
932 const char* msg = (const char*)http_response_get_body(response);
933 if (strnlen(msg, len + 1) > len)
934 return FALSE;
935
936 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
937 return arm_fill_gateway_parameters(arm, msg, len);
938}
939
940static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
941{
942 WINPR_ASSERT(response);
943 WINPR_ASSERT(retry);
944
945 *retry = FALSE;
946
947 BOOL rc = FALSE;
948
949 const size_t len = http_response_get_body_length(response);
950 const char* msg = (const char*)http_response_get_body(response);
951 if (strnlen(msg, len + 1) > len)
952 return FALSE;
953
954 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
955
956 WINPR_JSON* json = WINPR_JSON_ParseWithLength(msg, len);
957 if (json == NULL)
958 {
959 const char* error_ptr = WINPR_JSON_GetErrorPtr();
960 if (error_ptr != NULL)
961 WLog_Print(arm->log, WLOG_ERROR, "NullPoException: %s", error_ptr);
962
963 return FALSE;
964 }
965
966 WINPR_JSON* gateway_code_obj = WINPR_JSON_GetObjectItemCaseSensitive(json, "Code");
967 const char* gw_code_str = WINPR_JSON_GetStringValue(gateway_code_obj);
968 if (gw_code_str == NULL)
969 {
970 WLog_Print(arm->log, WLOG_ERROR, "Response has no \"Code\" property");
971 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
972 goto fail;
973 }
974
975 if (strcmp(gw_code_str, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
976 {
977 *retry = TRUE;
978 WINPR_JSON* message = WINPR_JSON_GetObjectItemCaseSensitive(json, "Message");
979 const char* msgstr = WINPR_JSON_GetStringValue(message);
980 if (!msgstr)
981 WLog_WARN(TAG, "Starting your VM. It may take up to 5 minutes");
982 else
983 WLog_WARN(TAG, "%s", msgstr);
984 }
985 else
986 {
987 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
988 goto fail;
989 }
990
991 rc = TRUE;
992fail:
993 WINPR_JSON_Delete(json);
994 return rc;
995}
996
997static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
998{
999 WINPR_ASSERT(retry);
1000
1001 if (!arm_fetch_wellknown(arm))
1002 {
1003 *retry = TRUE;
1004 return FALSE;
1005 }
1006
1007 *retry = FALSE;
1008
1009 char* message = NULL;
1010 BOOL rc = FALSE;
1011
1012 HttpResponse* response = NULL;
1013 long StatusCode = 0;
1014
1015 if (!http_context_set_uri(arm->http, "/api/arm/v2/connections/") ||
1016 !http_context_set_accept(arm->http, "application/json") ||
1017 !http_context_set_cache_control(arm->http, "no-cache") ||
1018 !http_context_set_pragma(arm->http, "no-cache") ||
1019 !http_context_set_connection(arm->http, "Keep-Alive") ||
1020 !http_context_set_user_agent(arm->http, FREERDP_USER_AGENT) ||
1021 !http_context_set_x_ms_user_agent(arm->http, FREERDP_USER_AGENT) ||
1022 !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
1023 FreeRDP_GatewayHostname)))
1024 goto arm_error;
1025
1026 if (!arm_tls_connect(arm, arm->tls, timeout))
1027 goto arm_error;
1028
1029 message = arm_create_request_json(arm);
1030 if (!message)
1031 goto arm_error;
1032
1033 if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
1034 goto arm_error;
1035
1036 response = http_response_recv(arm->tls, TRUE);
1037 if (!response)
1038 goto arm_error;
1039
1040 StatusCode = http_response_get_status_code(response);
1041 if (StatusCode == HTTP_STATUS_OK)
1042 {
1043 if (!arm_handle_request_ok(arm, response))
1044 goto arm_error;
1045 }
1046 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1047 {
1048 if (!arm_handle_bad_request(arm, response, retry))
1049 goto arm_error;
1050 }
1051 else
1052 {
1053 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1054 goto arm_error;
1055 }
1056
1057 rc = TRUE;
1058arm_error:
1059 http_response_free(response);
1060 free(message);
1061 return rc;
1062}
1063
1064#endif
1065
1066BOOL arm_resolve_endpoint(wLog* log, rdpContext* context, DWORD timeout)
1067{
1068#ifndef WITH_AAD
1069 WLog_Print(log, WLOG_ERROR, "arm gateway support not compiled in");
1070 return FALSE;
1071#else
1072
1073 if (!context)
1074 return FALSE;
1075
1076 if (!context->settings)
1077 return FALSE;
1078
1079 if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
1080 (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
1081 {
1082 WLog_Print(log, WLOG_ERROR, "loadBalanceInfo and RemoteApplicationProgram needed");
1083 return FALSE;
1084 }
1085
1086 rdpArm* arm = arm_new(context);
1087 if (!arm)
1088 return FALSE;
1089
1090 BOOL retry = FALSE;
1091 BOOL rc = FALSE;
1092 do
1093 {
1094 if (retry && rc)
1095 {
1096 freerdp* instance = context->instance;
1097 WINPR_ASSERT(instance);
1098 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance, "arm-transport",
1099 arm->gateway_retry, arm);
1100 arm->gateway_retry++;
1101 if (delay <= 0)
1102 break; /* error or no retry desired, abort loop */
1103 else
1104 {
1105 WLog_Print(arm->log, WLOG_DEBUG, "Delay for %" PRIdz "ms before next attempt",
1106 delay);
1107 while (delay > 0)
1108 {
1109 DWORD slp = (UINT32)delay;
1110 if (delay > UINT32_MAX)
1111 slp = UINT32_MAX;
1112 Sleep(slp);
1113 delay -= slp;
1114 }
1115 }
1116 }
1117 rc = arm_handle_request(arm, &retry, timeout);
1118
1119 } while (retry && rc);
1120 arm_free(arm);
1121 return rc;
1122#endif
1123}
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
Definition json.c:182
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition json.c:347
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition json.c:463
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
Definition json.c:153
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case insensitive matching.
Definition json.c:195
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition json.c:571
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition json.c:123
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 json.c:667
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition json.c:232
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition json.c:474
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition json.c:142
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition json.c:167
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition json.c:359
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition json.c:221
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition json.c:111
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.