FreeRDP
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 
61 struct rdp_arm
62 {
63  rdpContext* context;
64  rdpTls* tls;
65  HttpContext* http;
66 
67  UINT32 gateway_retry;
68  wLog* log;
69 };
70 
71 typedef struct rdp_arm rdpArm;
72 
73 #define TAG FREERDP_TAG("core.gateway.arm")
74 
75 #ifdef WITH_AAD
76 static 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 
161 static 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 
184 static 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);
250 out:
251  http_request_free(request);
252 
253  if (s)
254  Stream_SealLength(s);
255 
256  return s;
257 }
258 
259 static 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 
279 static 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 
290 static 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 
311 fail:
312  arm_free(arm);
313  return NULL;
314 }
315 
316 static 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);
345 arm_create_cleanup:
346  if (json)
347  WINPR_JSON_Delete(json);
348  free(lbi);
349  return message;
350 }
351 
366 static 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 
462 static 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 
484 static 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;
547 out:
548  free(finalOutput);
549  free(output);
550  free(encryptedPass);
551  free(wpasswd);
552  return ret;
553 }
554 
559 static 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 
627 static 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 
636 static 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 
665 static 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 
709 static 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 
795 out:
796  WINPR_JSON_Delete(json);
797  return ret;
798 }
799 
800 static 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 
831  BOOL status = freerdp_settings_set_pointer_len(
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 
872 endOfFunction:
873  return ret;
874 }
875 
876 static 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 
929 static 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 
940 static 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;
992 fail:
993  WINPR_JSON_Delete(json);
994  return rc;
995 }
996 
997 static 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;
1058 arm_error:
1059  http_response_free(response);
1060  free(message);
1061  return rc;
1062 }
1063 
1064 #endif
1065 
1066 BOOL 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_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition: json.c:125
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition: json.c:573
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition: json.c:349
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition: json.c:113
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:197
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:662
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition: json.c:476
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:155
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition: json.c:223
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition: json.c:144
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:184
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition: json.c:465
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition: json.c:169
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition: json.c:361
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition: json.c:234
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 const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string 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 BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.