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