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