FreeRDP
Loading...
Searching...
No Matches
redirection.c
1
22#include <freerdp/config.h>
23
24#include "settings.h"
25
26#include <winpr/crt.h>
27#include <winpr/string.h>
28#include <freerdp/log.h>
29#include <freerdp/crypto/certificate.h>
30#include <freerdp/redirection.h>
31#include <freerdp/utils/string.h>
32
33#include "../crypto/certificate.h"
34#include "redirection.h"
35#include "utils.h"
36
37#define TAG FREERDP_TAG("core.redirection")
38
39struct rdp_redirection
40{
41 UINT32 flags;
42 UINT32 sessionID;
43 BYTE* TsvUrl;
44 UINT32 TsvUrlLength;
45 char* Username;
46 char* Domain;
47 BYTE* Password;
48 UINT32 PasswordLength;
49 char* TargetFQDN;
50 BYTE* LoadBalanceInfo;
51 UINT32 LoadBalanceInfoLength;
52 char* TargetNetBiosName;
53 char* TargetNetAddress;
54 UINT32 TargetNetAddressesCount;
55 char** TargetNetAddresses;
56 UINT32 RedirectionGuidLength;
57 BYTE* RedirectionGuid;
58
59 rdpCertificate* TargetCertificate;
60};
61
62static void redirection_free_array(char*** what, UINT32* count)
63{
64 WINPR_ASSERT(what);
65 WINPR_ASSERT(count);
66
67 if (*what)
68 {
69 for (UINT32 x = 0; x < *count; x++)
70 free((*what)[x]);
71 free((void*)*what);
72 }
73
74 *what = nullptr;
75 *count = 0;
76}
77
78static void redirection_free_string(char** str)
79{
80 WINPR_ASSERT(str);
81 free(*str);
82 *str = nullptr;
83}
84
85static void redirection_free_data(BYTE** str, UINT32* length)
86{
87 WINPR_ASSERT(str);
88 free(*str);
89 if (length)
90 *length = 0;
91 *str = nullptr;
92}
93
94static BOOL redirection_copy_string(char** dst, const char* str)
95{
96 redirection_free_string(dst);
97 if (!str)
98 return TRUE;
99
100 *dst = _strdup(str);
101 return *dst != nullptr;
102}
103
104static BOOL redirection_copy_data(BYTE** dst, UINT32* plen, const BYTE* str, size_t len)
105{
106 redirection_free_data(dst, plen);
107
108 if (!str || (len == 0))
109 return TRUE;
110 if (len > UINT32_MAX)
111 return FALSE;
112
113 *dst = malloc(len);
114 if (!*dst)
115 return FALSE;
116 memcpy(*dst, str, len);
117 *plen = (UINT32)len;
118 return *dst != nullptr;
119}
120
121static BOOL redirection_copy_array(char*** dst, UINT32* plen, const char** str, size_t len)
122{
123 redirection_free_array(dst, plen);
124
125 if (len > UINT32_MAX)
126 return FALSE;
127 if (!str || (len == 0))
128 return TRUE;
129
130 *dst = (char**)calloc(len, sizeof(char*));
131 if (!*dst)
132 return FALSE;
133 *plen = (UINT32)len;
134
135 for (size_t x = 0; x < len; x++)
136 {
137 if (str[x])
138 (*dst)[x] = _strdup(str[x]);
139
140 if (!((*dst)[x]))
141 {
142 redirection_free_array(dst, plen);
143 return FALSE;
144 }
145 }
146
147 return *dst != nullptr;
148}
149
150static BOOL rdp_redirection_get_data(wStream* s, UINT32* pLength, const BYTE** pData)
151{
152 WINPR_ASSERT(pLength);
153 WINPR_ASSERT(pData);
154
155 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
156 return FALSE;
157
158 Stream_Read_UINT32(s, *pLength);
159
160 if (!Stream_CheckAndLogRequiredLength(TAG, s, *pLength))
161 return FALSE;
162
163 *pData = Stream_ConstPointer(s);
164 Stream_Seek(s, *pLength);
165 return TRUE;
166}
167
168static BOOL rdp_redirection_read_unicode_string(wStream* s, char** str, size_t maxLength)
169{
170 UINT32 length = 0;
171 const BYTE* data = nullptr;
172
173 if (!rdp_redirection_get_data(s, &length, &data))
174 return FALSE;
175
176 const WCHAR* wstr = WINPR_PACKED_ALIGN_CAST(const WCHAR*, data);
177
178 if ((length % 2) || length < 2 || length > maxLength)
179 {
180 WLog_ERR(TAG, "failure: invalid unicode string length: %" PRIu32 "", length);
181 return FALSE;
182 }
183
184 if (wstr[length / 2 - 1])
185 {
186 WLog_ERR(TAG, "failure: unterminated unicode string");
187 return FALSE;
188 }
189
190 redirection_free_string(str);
191 *str = ConvertWCharNToUtf8Alloc(wstr, length / sizeof(WCHAR), nullptr);
192 if (!*str)
193 {
194 WLog_ERR(TAG, "failure: string conversion failed");
195 return FALSE;
196 }
197
198 return TRUE;
199}
200
201static BOOL rdp_redirection_write_data(wStream* s, size_t length, const void* data)
202{
203 WINPR_ASSERT(data || (length == 0));
204 WINPR_ASSERT(length <= UINT32_MAX);
205
206 if (!Stream_EnsureRemainingCapacity(s, 4))
207 return FALSE;
208
209 Stream_Write_UINT32(s, (UINT32)length);
210
211 if (!Stream_EnsureRemainingCapacity(s, length))
212 return FALSE;
213
214 Stream_Write(s, data, length);
215 return TRUE;
216}
217
218static BOOL rdp_redirection_write_base64_wchar(WINPR_ATTR_UNUSED UINT32 flag, wStream* s,
219 size_t length, const void* data)
220{
221 BOOL rc = FALSE;
222
223 char* base64 = crypto_base64_encode(data, length);
224 if (!base64)
225 return FALSE;
226
227 size_t wbase64len = 0;
228 WCHAR* wbase64 = ConvertUtf8ToWCharAlloc(base64, &wbase64len);
229 free(base64);
230 if (!wbase64)
231 return FALSE;
232
233 rc = rdp_redirection_write_data(s, wbase64len * sizeof(WCHAR), wbase64);
234 free(wbase64);
235 return rc;
236}
237
238static BOOL rdp_redirection_read_base64_wchar(UINT32 flag, wStream* s, UINT32* pLength,
239 BYTE** pData)
240{
241 BOOL rc = FALSE;
242 char buffer[64] = WINPR_C_ARRAY_INIT;
243 const BYTE* ptr = nullptr;
244
245 if (!rdp_redirection_get_data(s, pLength, &ptr))
246 return FALSE;
247 const WCHAR* wchar = WINPR_PACKED_ALIGN_CAST(const WCHAR*, ptr);
248
249 size_t utf8_len = 0;
250 char* utf8 = ConvertWCharNToUtf8Alloc(wchar, *pLength / sizeof(WCHAR), &utf8_len);
251 if (!utf8)
252 goto fail;
253
254 redirection_free_data(pData, nullptr);
255
256 utf8_len = strnlen(utf8, utf8_len);
257 *pData = nullptr;
258 if (utf8_len > 0)
259 *pData = calloc(utf8_len, sizeof(BYTE));
260 if (!*pData)
261 goto fail;
262
263 {
264 size_t rlen = utf8_len;
265 size_t wpos = 0;
266 char* saveptr = nullptr;
267 char* tok = strtok_s(utf8, "\r\n", &saveptr);
268 while (tok)
269 {
270 const size_t len = strnlen(tok, rlen);
271 rlen -= len;
272
273 size_t bplen = 0;
274 BYTE* bptr = nullptr;
275 crypto_base64_decode(tok, len, &bptr, &bplen);
276 if (!bptr)
277 goto fail;
278 memcpy(&(*pData)[wpos], bptr, bplen);
279 wpos += bplen;
280 free(bptr);
281
282 tok = strtok_s(nullptr, "\r\n", &saveptr);
283 }
284 if (wpos > UINT32_MAX)
285 goto fail;
286
287 *pLength = (UINT32)wpos;
288
289 WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
290 }
291
292 rc = TRUE;
293fail:
294 if (!rc)
295 WLog_ERR(TAG, "failed to read base64 data");
296 free(utf8);
297 return rc;
298}
299
300static BOOL rdp_target_cert_get_element(wStream* s, UINT32* pType, UINT32* pEncoding,
301 const BYTE** ptr, size_t* pLength)
302{
303 WINPR_ASSERT(pType);
304 WINPR_ASSERT(pEncoding);
305 WINPR_ASSERT(ptr);
306 WINPR_ASSERT(pLength);
307
308 if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
309 return FALSE;
310
311 UINT32 type = 0;
312 UINT32 encoding = 0;
313 UINT32 elementSize = 0;
314
315 Stream_Read_UINT32(s, type);
316 Stream_Read_UINT32(s, encoding);
317 Stream_Read_UINT32(s, elementSize);
318
319 if (!Stream_CheckAndLogRequiredLength(TAG, s, elementSize))
320 return FALSE;
321
322 *ptr = Stream_ConstPointer(s);
323 *pLength = elementSize;
324 Stream_Seek(s, elementSize);
325
326 *pType = type;
327 *pEncoding = encoding;
328 return TRUE;
329}
330
331static BOOL rdp_target_cert_write_element(wStream* s, UINT32 Type, UINT32 Encoding,
332 const BYTE* data, size_t length)
333{
334 WINPR_ASSERT(data || (length == 0));
335 WINPR_ASSERT(length <= UINT32_MAX);
336
337 if (!Stream_EnsureRemainingCapacity(s, 12))
338 return FALSE;
339
340 Stream_Write_UINT32(s, Type);
341 Stream_Write_UINT32(s, Encoding);
342 Stream_Write_UINT32(s, (UINT32)length);
343
344 if (!Stream_EnsureRemainingCapacity(s, length))
345 return FALSE;
346
347 Stream_Write(s, data, length);
348 return TRUE;
349}
350
351BOOL rdp_redirection_read_target_cert(rdpCertificate** ptargetCertificate, const BYTE* data,
352 size_t length)
353{
354 WINPR_ASSERT(ptargetCertificate);
355
356 wStream sbuffer = WINPR_C_ARRAY_INIT;
357 wStream* s = Stream_StaticConstInit(&sbuffer, data, length);
358
359 freerdp_certificate_free(*ptargetCertificate);
360 *ptargetCertificate = nullptr;
361
362 size_t plength = 0;
363 const BYTE* ptr = nullptr;
364 while (Stream_GetRemainingLength(s) > 0)
365 {
366 UINT32 type = 0;
367 UINT32 encoding = 0;
368 if (!rdp_target_cert_get_element(s, &type, &encoding, &ptr, &plength))
369 return FALSE;
370
371 switch (type)
372 {
373 case CERT_cert_file_element:
374 if (encoding == ENCODING_TYPE_ASN1_DER)
375 {
376 if (*ptargetCertificate)
377 WLog_WARN(TAG, "Duplicate TargetCertificate in data detected!");
378 else
379 {
380 *ptargetCertificate = freerdp_certificate_new_from_der(ptr, plength);
381 if (!*ptargetCertificate)
382 WLog_ERR(TAG, "TargetCertificate parsing DER data failed");
383 }
384 }
385 else
386 {
387 WLog_ERR(TAG,
388 "TargetCertificate data in unknown encoding %" PRIu32 " detected!",
389 encoding);
390 }
391 break;
392 default: /* ignore unknown fields */
393 WLog_VRB(TAG,
394 "Unknown TargetCertificate field type %" PRIu32 ", encoding %" PRIu32
395 " of length %" PRIuz,
396 type, encoding, plength);
397 break;
398 }
399 }
400
401 return *ptargetCertificate != nullptr;
402}
403
404static BOOL rdp_redirection_write_target_cert(wStream* s, const rdpRedirection* redirection)
405{
406 BOOL rc = FALSE;
407 WINPR_ASSERT(redirection);
408
409 const rdpCertificate* cert = redirection->TargetCertificate;
410 if (!cert)
411 return FALSE;
412
413 size_t derlen = 0;
414
415 BYTE* der = freerdp_certificate_get_der(cert, &derlen);
416 if (!rdp_target_cert_write_element(s, CERT_cert_file_element, ENCODING_TYPE_ASN1_DER, der,
417 derlen))
418 goto fail;
419
420 rc = TRUE;
421
422fail:
423 free(der);
424 return rc;
425}
426
427static BOOL rdp_redireciton_write_target_cert_stream(wStream* s, const rdpRedirection* redirection)
428{
429 BOOL rc = FALSE;
430 wStream* serialized = Stream_New(nullptr, 2048);
431 if (!serialized)
432 goto fail;
433
434 if (!rdp_redirection_write_target_cert(serialized, redirection))
435 goto fail;
436
437 rc = rdp_redirection_write_base64_wchar(
438 LB_TARGET_CERTIFICATE, s, Stream_GetPosition(serialized), Stream_Buffer(serialized));
439
440fail:
441 Stream_Free(serialized, TRUE);
442 return rc;
443}
444
445static BOOL rdp_redirection_read_target_cert_stream(wStream* s, rdpRedirection* redirection)
446{
447 UINT32 length = 0;
448 BYTE* ptr = nullptr;
449
450 WINPR_ASSERT(redirection);
451
452 BOOL rc = FALSE;
453 if (rdp_redirection_read_base64_wchar(LB_TARGET_CERTIFICATE, s, &length, &ptr))
454 rc = rdp_redirection_read_target_cert(&redirection->TargetCertificate, ptr, length);
455 free(ptr);
456 return rc;
457}
458
459BOOL rdp_set_target_certificate(rdpSettings* settings, const rdpCertificate* tcert)
460{
461 rdpCertificate* cert = freerdp_certificate_clone(tcert);
462 if (!freerdp_settings_set_pointer(settings, FreeRDP_RedirectionTargetCertificate, cert))
463 return FALSE;
464
465 BOOL pres = FALSE;
466 size_t length = 0;
467 char* pem = freerdp_certificate_get_pem(cert, &length);
468 if (pem && (length <= UINT32_MAX))
469 {
470 pres =
471 freerdp_settings_set_string_len(settings, FreeRDP_RedirectionAcceptedCert, pem, length);
472 if (pres)
473 pres = freerdp_settings_set_uint32(settings, FreeRDP_RedirectionAcceptedCertLength,
474 (UINT32)length);
475 }
476 free(pem);
477 return pres;
478}
479
480int rdp_redirection_apply_settings(rdpRdp* rdp)
481{
482 rdpSettings* settings = nullptr;
483 rdpRedirection* redirection = nullptr;
484
485 if (!rdp_reset_runtime_settings(rdp))
486 return -1;
487
488 settings = rdp->settings;
489 WINPR_ASSERT(settings);
490
491 redirection = rdp->redirection;
492 WINPR_ASSERT(redirection);
493
494 settings->RedirectionFlags = redirection->flags;
495 settings->RedirectedSessionId = redirection->sessionID;
496
497 if (settings->RedirectionFlags & LB_TARGET_NET_ADDRESS)
498 {
499 if (!freerdp_settings_set_string(settings, FreeRDP_TargetNetAddress,
500 redirection->TargetNetAddress))
501 return -1;
502 }
503
504 if (settings->RedirectionFlags & LB_USERNAME)
505 {
506 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionUsername,
507 redirection->Username))
508 return -1;
509 }
510
511 if (settings->RedirectionFlags & LB_DOMAIN)
512 {
513 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionDomain, redirection->Domain))
514 return -1;
515 }
516
517 if (settings->RedirectionFlags & LB_PASSWORD)
518 {
519 /* Password may be a cookie without a null terminator */
520 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword,
521 redirection->Password, redirection->PasswordLength))
522 return -1;
523 }
524
525 if (settings->RedirectionFlags & LB_DONTSTOREUSERNAME)
526 {
527 // TODO
528 }
529
530 if (settings->RedirectionFlags & LB_SMARTCARD_LOGON)
531 {
532 // TODO
533 }
534
535 if (settings->RedirectionFlags & LB_NOREDIRECT)
536 {
537 // TODO
538 }
539
540 if (settings->RedirectionFlags & LB_TARGET_FQDN)
541 {
542 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionTargetFQDN,
543 redirection->TargetFQDN))
544 return -1;
545 }
546
547 if (settings->RedirectionFlags & LB_TARGET_NETBIOS_NAME)
548 {
549 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionTargetNetBiosName,
550 redirection->TargetNetBiosName))
551 return -1;
552 }
553
554 if (settings->RedirectionFlags & LB_TARGET_NET_ADDRESSES)
555 {
556 if (!freerdp_target_net_addresses_copy(settings, redirection->TargetNetAddresses,
557 redirection->TargetNetAddressesCount))
558 return -1;
559 }
560
561 if (settings->RedirectionFlags & LB_CLIENT_TSV_URL)
562 {
563 /* TsvUrl may not contain a null terminator */
564 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionTsvUrl,
565 redirection->TsvUrl, redirection->TsvUrlLength))
566 return -1;
567
568 const size_t lblen = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
569 const char* lb = freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
570 if (lblen > 0)
571 {
572 BOOL valid = TRUE;
573 size_t tsvlen = 0;
574
575 char* tsv =
576 ConvertWCharNToUtf8Alloc(WINPR_PACKED_ALIGN_CAST(const WCHAR*, redirection->TsvUrl),
577 redirection->TsvUrlLength / sizeof(WCHAR), &tsvlen);
578 if (!tsv || !lb)
579 valid = FALSE;
580 else if (tsvlen != lblen)
581 valid = FALSE;
582 else if (memcmp(tsv, lb, lblen) != 0)
583 valid = FALSE;
584
585 if (!valid)
586 {
587 WLog_ERR(TAG,
588 "[redirection] Expected TsvUrl '%s' [%" PRIuz "], but got '%s' [%" PRIuz
589 "]",
590 lb, lblen, tsv, tsvlen);
591 }
592 free(tsv);
593
594 if (!valid)
595 return -2;
596 }
597 }
598
599 if (settings->RedirectionFlags & LB_SERVER_TSV_CAPABLE)
600 {
601 // TODO
602 }
603
604 if (settings->RedirectionFlags & LB_LOAD_BALANCE_INFO)
605 {
606 /* LoadBalanceInfo may not contain a null terminator */
607 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo,
608 redirection->LoadBalanceInfo,
609 redirection->LoadBalanceInfoLength))
610 return -1;
611 }
612 else
613 {
618 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, nullptr, 0))
619 return -1;
620 }
621
622 if (settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED)
623 {
624 // TODO
625 }
626
627 if (settings->RedirectionFlags & LB_REDIRECTION_GUID)
628 {
629 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid,
630 redirection->RedirectionGuid,
631 redirection->RedirectionGuidLength))
632 return -1;
633 }
634
635 if (settings->RedirectionFlags & LB_TARGET_CERTIFICATE)
636 {
637 if (!rdp_set_target_certificate(settings, redirection->TargetCertificate))
638 return -1;
639 }
640
641 return 0;
642}
643
644static BOOL rdp_redirection_read_data(UINT32 flag, wStream* s, UINT32* pLength, BYTE** pData)
645{
646 char buffer[64] = WINPR_C_ARRAY_INIT;
647 const BYTE* ptr = nullptr;
648
649 if (!rdp_redirection_get_data(s, pLength, &ptr))
650 return FALSE;
651
652 redirection_free_data(pData, nullptr);
653 *pData = (BYTE*)malloc(*pLength);
654
655 if (!*pData)
656 return FALSE;
657 memcpy(*pData, ptr, *pLength);
658
659 WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
660
661 return TRUE;
662}
663
664static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
665{
666 char buffer[256] = WINPR_C_ARRAY_INIT;
667 UINT16 flags = 0;
668 UINT16 length = 0;
669 rdpRedirection* redirection = rdp->redirection;
670
671 if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
672 return STATE_RUN_FAILED;
673
674 Stream_Read_UINT16(s, flags); /* flags (2 bytes) */
675 if (flags != SEC_REDIRECTION_PKT)
676 {
677 char buffer1[1024] = WINPR_C_ARRAY_INIT;
678 char buffer2[1024] = WINPR_C_ARRAY_INIT;
679 WLog_ERR(TAG, "received invalid flags=%s, expected %s",
680 rdp_security_flag_string(flags, buffer1, sizeof(buffer1)),
681 rdp_security_flag_string(SEC_REDIRECTION_PKT, buffer2, sizeof(buffer2)));
682 return STATE_RUN_FAILED;
683 }
684 Stream_Read_UINT16(s, length); /* length (2 bytes) */
685 Stream_Read_UINT32(s, redirection->sessionID); /* sessionID (4 bytes) */
686 Stream_Read_UINT32(s, redirection->flags); /* redirFlags (4 bytes) */
687 WLog_INFO(TAG,
688 "flags: 0x%04" PRIX16 ", length: %" PRIu16 ", sessionID: 0x%08" PRIX32
689 ", redirFlags: %s [0x%08" PRIX32 "]",
690 flags, length, redirection->sessionID,
691 rdp_redirection_flags_to_string(redirection->flags, buffer, sizeof(buffer)),
692 redirection->flags);
693
694 /* Although MS-RDPBCGR does not mention any length constraints limits for the
695 * variable length null-terminated unicode strings in the RDP_SERVER_REDIRECTION_PACKET
696 * structure we will use the following limits in bytes including the null terminator:
697 *
698 * TargetNetAddress: 80 bytes
699 * UserName: 512 bytes
700 * Domain: 52 bytes
701 * Password(Cookie): 512 bytes
702 * TargetFQDN: 512 bytes
703 * TargetNetBiosName: 32 bytes
704 */
705
706 if (redirection->flags & LB_TARGET_NET_ADDRESS)
707 {
708 if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddress), 80))
709 return STATE_RUN_FAILED;
710 if (!utils_is_valid_ip(redirection->TargetNetAddress))
711 return STATE_RUN_FAILED;
712 }
713
714 if (redirection->flags & LB_LOAD_BALANCE_INFO)
715 {
716 /* See [MSFT-SDLBTS] (a.k.a. TS_Session_Directory.doc)
717 * load balance info example data:
718 * 0000 43 6f 6f 6b 69 65 3a 20 6d 73 74 73 3d 32 31 33 Cookie: msts=213
719 * 0010 34 30 32 36 34 33 32 2e 31 35 36 32 39 2e 30 30 4026432.15629.00
720 * 0020 30 30 0d 0a 00..
721 */
722 if (!rdp_redirection_read_data(LB_LOAD_BALANCE_INFO, s, &redirection->LoadBalanceInfoLength,
723 &redirection->LoadBalanceInfo))
724 return STATE_RUN_FAILED;
725 }
726
727 if (redirection->flags & LB_USERNAME)
728 {
729 if (!rdp_redirection_read_unicode_string(s, &(redirection->Username), 512))
730 return STATE_RUN_FAILED;
731 if (winpr_str_has_newlines(redirection->Username))
732 return STATE_RUN_FAILED;
733 WLog_DBG(TAG, "Username: %s", redirection->Username);
734 }
735
736 if (redirection->flags & LB_DOMAIN)
737 {
738 if (!rdp_redirection_read_unicode_string(s, &(redirection->Domain), 52))
739 return STATE_RUN_FAILED;
740 if (winpr_str_has_newlines(redirection->Domain))
741 return STATE_RUN_FAILED;
742 WLog_DBG(TAG, "Domain: %s", redirection->Domain);
743 }
744
745 if (redirection->flags & LB_PASSWORD)
746 {
747 /* Note: Password is a variable-length array of bytes containing the
748 * password used by the user in Unicode format, including a null-terminator
749 * or (!) or a cookie value that MUST be passed to the target server on
750 * successful connection.
751 * Since the format of the password cookie (probably some salted hash) is
752 * currently unknown we'll treat it as opaque data. All cookies seen so far
753 * are 120 bytes including \0\0 termination.
754 * Here is an observed example of a redirection password cookie:
755 *
756 * 0000 02 00 00 80 44 53 48 4c 60 ab 69 2f 07 d6 9e 2d ....DSHL`.i/...-
757 * 0010 f0 3a 97 3b a9 c5 ec 7e 66 bd b3 84 6c b1 ef b9 .:.;...~f...l...
758 * 0020 b6 82 4e cc 3a df 64 b7 7b 25 04 54 c2 58 98 f8 ..N.:.d.{%.T.X..
759 * 0030 97 87 d4 93 c7 c1 e1 5b c2 85 f8 22 49 1f 81 88 .......[..."I...
760 * 0040 43 44 83 f6 9a 72 40 24 dc 4d 43 cb d9 92 3c 8f CD...r@$.MC...<.
761 * 0050 3a 37 5c 77 13 a0 72 3c 72 08 64 2a 29 fb dc eb :7\w..r<r.d*)...
762 * 0060 0d 2b 06 b4 c6 08 b4 73 34 16 93 62 6d 24 e9 93 .+.....s4..bm$..
763 * 0070 97 27 7b dd 9a 72 00 00 .'{..r..
764 *
765 * Notwithstanding the above, we'll allocated an additional zero WCHAR at the
766 * end of the buffer which won't get counted in PasswordLength.
767 */
768 if (!rdp_redirection_read_data(LB_PASSWORD, s, &redirection->PasswordLength,
769 &redirection->Password))
770 return STATE_RUN_FAILED;
771
772 /* [MS-RDPBCGR] specifies 512 bytes as the upper limit for the password length
773 * including the null terminatior(s). This should also be enough for the unknown
774 * password cookie format (see previous comment).
775 */
776 if ((redirection->flags & LB_PASSWORD_IS_PK_ENCRYPTED) == 0)
777 {
778 const size_t charLen = redirection->PasswordLength / sizeof(WCHAR);
779 if (redirection->PasswordLength > LB_PASSWORD_MAX_LENGTH)
780 {
781 WLog_ERR(TAG, "LB_PASSWORD: %" PRIuz " exceeds limit of %d", charLen,
782 LB_PASSWORD_MAX_LENGTH);
783 return STATE_RUN_FAILED;
784 }
785
786 /* Ensure the text password is '\0' terminated */
787 if (_wcsnlen(WINPR_PACKED_ALIGN_CAST(const WCHAR*, redirection->Password), charLen) ==
788 charLen)
789 {
790 WLog_ERR(TAG, "LB_PASSWORD: missing '\\0' termination");
791 return STATE_RUN_FAILED;
792 }
793 }
794 }
795
796 if (redirection->flags & LB_TARGET_FQDN)
797 {
798 if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetFQDN), 512))
799 return STATE_RUN_FAILED;
800 if (!winpr_str_is_valid_url(redirection->TargetFQDN))
801 return STATE_RUN_FAILED;
802 WLog_DBG(TAG, "TargetFQDN: %s", redirection->TargetFQDN);
803 }
804
805 if (redirection->flags & LB_TARGET_NETBIOS_NAME)
806 {
807 if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetBiosName), 32))
808 return STATE_RUN_FAILED;
809 if (!winpr_str_is_valid_url(redirection->TargetNetBiosName))
810 return STATE_RUN_FAILED;
811 WLog_DBG(TAG, "TargetNetBiosName: %s", redirection->TargetNetBiosName);
812 }
813
814 if (redirection->flags & LB_CLIENT_TSV_URL)
815 {
816 if (!rdp_redirection_read_data(LB_CLIENT_TSV_URL, s, &redirection->TsvUrlLength,
817 &redirection->TsvUrl))
818 return STATE_RUN_FAILED;
819 }
820
821 if (redirection->flags & LB_REDIRECTION_GUID)
822 {
823 if (!rdp_redirection_read_data(LB_REDIRECTION_GUID, s, &redirection->RedirectionGuidLength,
824 &redirection->RedirectionGuid))
825 return STATE_RUN_FAILED;
826 }
827
828 if (redirection->flags & LB_TARGET_CERTIFICATE)
829 {
830 if (!rdp_redirection_read_target_cert_stream(s, redirection))
831 return STATE_RUN_FAILED;
832 }
833
834 if (redirection->flags & LB_TARGET_NET_ADDRESSES)
835 {
836 UINT32 targetNetAddressesLength = 0;
837 UINT32 TargetNetAddressesCount = 0;
838
839 if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
840 return STATE_RUN_FAILED;
841
842 Stream_Read_UINT32(s, targetNetAddressesLength);
843 Stream_Read_UINT32(s, TargetNetAddressesCount);
844
845 /* sanity check: the whole packet has a length limit of UINT16_MAX
846 * each TargetNetAddress is a WCHAR string, so minimum length 2 bytes
847 */
848 const size_t size = TargetNetAddressesCount * sizeof(WCHAR);
849 if ((size > Stream_GetRemainingLength(s)) || (size > targetNetAddressesLength))
850 {
851 WLog_ERR(TAG,
852 "Invalid RDP_SERVER_REDIRECTION_PACKET::TargetNetAddressLength %" PRIuz
853 ", sanity limit is %" PRIuz,
854 TargetNetAddressesCount * sizeof(WCHAR), Stream_GetRemainingLength(s));
855 return STATE_RUN_FAILED;
856 }
857
858 redirection_free_array(&redirection->TargetNetAddresses,
859 &redirection->TargetNetAddressesCount);
860 if (TargetNetAddressesCount > 0)
861 {
862 redirection->TargetNetAddresses =
863 (char**)calloc(TargetNetAddressesCount, sizeof(char*));
864
865 if (!redirection->TargetNetAddresses)
866 {
867 WLog_ERR(TAG, "TargetNetAddresses %" PRIu32 " failed to allocate",
868 TargetNetAddressesCount);
869 return STATE_RUN_FAILED;
870 }
871 }
872 redirection->TargetNetAddressesCount = TargetNetAddressesCount;
873
874 WLog_DBG(TAG, "TargetNetAddressesCount: %" PRIu32 "", TargetNetAddressesCount);
875
876 for (UINT32 i = 0; i < TargetNetAddressesCount; i++)
877 {
878 if (!rdp_redirection_read_unicode_string(s, &(redirection->TargetNetAddresses[i]), 80))
879 return STATE_RUN_FAILED;
880 if (!utils_is_valid_ip(redirection->TargetNetAddresses[i]))
881 return STATE_RUN_FAILED;
882 WLog_DBG(TAG, "TargetNetAddresses[%" PRIu32 "]: %s", i,
883 redirection->TargetNetAddresses[i]);
884 }
885 }
886
887 if (Stream_GetRemainingLength(s) >= 8)
888 {
889 /* some versions of windows don't included this padding before closing the connection */
890 Stream_Seek(s, 8); /* pad (8 bytes) */
891 }
892
893 if (redirection->flags & LB_NOREDIRECT)
894 return STATE_RUN_SUCCESS;
895
896 return STATE_RUN_REDIRECT;
897}
898
899state_run_t rdp_recv_enhanced_security_redirection_packet(rdpRdp* rdp, wStream* s)
900{
901 state_run_t status = STATE_RUN_SUCCESS;
902
903 if (!Stream_SafeSeek(s, 2)) /* pad2Octets (2 bytes) */
904 return STATE_RUN_FAILED;
905
906 status = rdp_recv_server_redirection_pdu(rdp, s);
907
908 if (state_run_failed(status))
909 {
910 WLog_Print(rdp->log, WLOG_ERROR, "redirection packet invalid, aborting");
911 return status;
912 }
913
914 if (Stream_GetRemainingLength(s) >= 1)
915 {
916 /* this field is optional, and its absence is not an error */
917 Stream_Seek(s, 1); /* pad2Octets (1 byte) */
918 }
919
920 return status;
921}
922
923rdpRedirection* redirection_new(void)
924{
925 rdpRedirection* redirection = (rdpRedirection*)calloc(1, sizeof(rdpRedirection));
926
927 if (redirection)
928 {
929 }
930
931 return redirection;
932}
933
934void redirection_free(rdpRedirection* redirection)
935{
936 if (redirection)
937 {
938 redirection_free_data(&redirection->TsvUrl, &redirection->TsvUrlLength);
939 redirection_free_string(&redirection->Username);
940 redirection_free_string(&redirection->Domain);
941 redirection_free_string(&redirection->TargetFQDN);
942 redirection_free_string(&redirection->TargetNetBiosName);
943 redirection_free_string(&redirection->TargetNetAddress);
944 redirection_free_data(&redirection->LoadBalanceInfo, &redirection->LoadBalanceInfoLength);
945 redirection_free_data(&redirection->Password, &redirection->PasswordLength);
946 redirection_free_data(&redirection->RedirectionGuid, &redirection->RedirectionGuidLength);
947 freerdp_certificate_free(redirection->TargetCertificate);
948 redirection_free_array(&redirection->TargetNetAddresses,
949 &redirection->TargetNetAddressesCount);
950
951 free(redirection);
952 }
953}
954
955static SSIZE_T redir_write_string(WINPR_ATTR_UNUSED UINT32 flag, wStream* s, const char* str)
956{
957 const SSIZE_T devNameWLen = ConvertUtf8ToWChar(str, nullptr, 0);
958 if (devNameWLen < 0)
959 return -1;
960 const size_t length = WINPR_ASSERTING_INT_CAST(size_t, devNameWLen) + 1;
961 const size_t slen = strlen(str);
962 if (!Stream_EnsureRemainingCapacity(s, 4ull + length * sizeof(WCHAR)))
963 return -1;
964
965 const size_t pos = Stream_GetPosition(s);
966 Stream_Write_UINT32(s, (UINT32)length * sizeof(WCHAR));
967 if (Stream_Write_UTF16_String_From_UTF8(s, length, str, slen, TRUE) < 0)
968 return -1;
969 return (SSIZE_T)(Stream_GetPosition(s) - pos);
970}
971
972static BOOL redir_write_data(WINPR_ATTR_UNUSED UINT32 flag, wStream* s, UINT32 length,
973 const BYTE* data)
974{
975 if (!Stream_EnsureRemainingCapacity(s, 4ull + length))
976 return FALSE;
977
978 Stream_Write_UINT32(s, length);
979 Stream_Write(s, data, length);
980 return TRUE;
981}
982
983BOOL rdp_write_enhanced_security_redirection_packet(wStream* s, const rdpRedirection* redirection)
984{
985 BOOL rc = FALSE;
986
987 WINPR_ASSERT(s);
988 WINPR_ASSERT(redirection);
989
990 if (!Stream_EnsureRemainingCapacity(s, 14))
991 goto fail;
992
993 Stream_Write_UINT16(s, 0);
994
995 {
996 const size_t start = Stream_GetPosition(s);
997 Stream_Write_UINT16(s, SEC_REDIRECTION_PKT);
998 const size_t lengthOffset = Stream_GetPosition(s);
999 Stream_Seek_UINT16(s); /* placeholder for length */
1000
1001 if (redirection->sessionID)
1002 Stream_Write_UINT32(s, redirection->sessionID);
1003 else
1004 Stream_Write_UINT32(s, 0);
1005
1006 Stream_Write_UINT32(s, redirection->flags);
1007
1008 if (redirection->flags & LB_TARGET_NET_ADDRESS)
1009 {
1010 if (redir_write_string(LB_TARGET_NET_ADDRESS, s, redirection->TargetNetAddress) < 0)
1011 goto fail;
1012 }
1013
1014 if (redirection->flags & LB_LOAD_BALANCE_INFO)
1015 {
1016 const UINT32 length = 13 + redirection->LoadBalanceInfoLength + 2;
1017 if (!Stream_EnsureRemainingCapacity(s, length))
1018 goto fail;
1019 Stream_Write_UINT32(s, length);
1020 Stream_Write(s, "Cookie: msts=", 13);
1021 Stream_Write(s, redirection->LoadBalanceInfo, redirection->LoadBalanceInfoLength);
1022 Stream_Write_UINT8(s, 0x0d);
1023 Stream_Write_UINT8(s, 0x0a);
1024 }
1025
1026 if (redirection->flags & LB_USERNAME)
1027 {
1028 if (redir_write_string(LB_USERNAME, s, redirection->Username) < 0)
1029 goto fail;
1030 }
1031
1032 if (redirection->flags & LB_DOMAIN)
1033 {
1034 if (redir_write_string(LB_DOMAIN, s, redirection->Domain) < 0)
1035 goto fail;
1036 }
1037
1038 if (redirection->flags & LB_PASSWORD)
1039 {
1040 /* Password is either UNICODE or opaque data */
1041 if (!redir_write_data(LB_PASSWORD, s, redirection->PasswordLength,
1042 redirection->Password))
1043 goto fail;
1044 }
1045
1046 if (redirection->flags & LB_TARGET_FQDN)
1047 {
1048 if (redir_write_string(LB_TARGET_FQDN, s, redirection->TargetFQDN) < 0)
1049 goto fail;
1050 }
1051
1052 if (redirection->flags & LB_TARGET_NETBIOS_NAME)
1053 {
1054 if (redir_write_string(LB_TARGET_NETBIOS_NAME, s, redirection->TargetNetBiosName) < 0)
1055 goto fail;
1056 }
1057
1058 if (redirection->flags & LB_CLIENT_TSV_URL)
1059 {
1060 if (!redir_write_data(LB_CLIENT_TSV_URL, s, redirection->TsvUrlLength,
1061 redirection->TsvUrl))
1062 goto fail;
1063 }
1064
1065 if (redirection->flags & LB_REDIRECTION_GUID)
1066 {
1067 if (!redir_write_data(LB_REDIRECTION_GUID, s, redirection->RedirectionGuidLength,
1068 redirection->RedirectionGuid))
1069 goto fail;
1070 }
1071
1072 if (redirection->flags & LB_TARGET_CERTIFICATE)
1073 {
1074 if (!rdp_redireciton_write_target_cert_stream(s, redirection))
1075 goto fail;
1076 }
1077
1078 if (redirection->flags & LB_TARGET_NET_ADDRESSES)
1079 {
1080 UINT32 length = sizeof(UINT32);
1081
1082 if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT32)))
1083 goto fail;
1084
1085 const size_t lstart = Stream_GetPosition(s);
1086 Stream_Seek_UINT32(s); /* length of field */
1087 Stream_Write_UINT32(s, redirection->TargetNetAddressesCount);
1088 for (UINT32 i = 0; i < redirection->TargetNetAddressesCount; i++)
1089 {
1090 const SSIZE_T rcc = redir_write_string(LB_TARGET_NET_ADDRESSES, s,
1091 redirection->TargetNetAddresses[i]);
1092 if (rcc < 0)
1093 goto fail;
1094 length += (UINT32)rcc;
1095 }
1096
1097 /* Write length field */
1098 const size_t lend = Stream_GetPosition(s);
1099 if (!Stream_SetPosition(s, lstart))
1100 goto fail;
1101 Stream_Write_UINT32(s, length);
1102 if (!Stream_SetPosition(s, lend))
1103 goto fail;
1104 }
1105
1106 /* Padding 8 bytes */
1107 if (!Stream_EnsureRemainingCapacity(s, 8))
1108 goto fail;
1109 Stream_Zero(s, 8);
1110
1111 {
1112 const size_t end = Stream_GetPosition(s);
1113 if (!Stream_SetPosition(s, lengthOffset))
1114 goto fail;
1115 Stream_Write_UINT16(s, (UINT16)(end - start));
1116 if (!Stream_SetPosition(s, end))
1117 goto fail;
1118 }
1119 }
1120
1121 rc = TRUE;
1122fail:
1123 return rc;
1124}
1125
1126BOOL redirection_settings_are_valid(rdpRedirection* redirection, UINT32* pFlags)
1127{
1128 UINT32 flags = 0;
1129
1130 WINPR_ASSERT(redirection);
1131
1132 if (redirection->flags & LB_CLIENT_TSV_URL)
1133 {
1134 if (!redirection->TsvUrl || (redirection->TsvUrlLength == 0))
1135 flags |= LB_CLIENT_TSV_URL;
1136 }
1137
1138 if (redirection->flags & LB_SERVER_TSV_CAPABLE)
1139 {
1140 if ((redirection->flags & LB_CLIENT_TSV_URL) == 0)
1141 flags |= LB_SERVER_TSV_CAPABLE;
1142 }
1143
1144 if (redirection->flags & LB_USERNAME)
1145 {
1146 if (utils_str_is_empty(redirection->Username))
1147 flags |= LB_USERNAME;
1148 }
1149
1150 if (redirection->flags & LB_DOMAIN)
1151 {
1152 if (utils_str_is_empty(redirection->Domain))
1153 flags |= LB_DOMAIN;
1154 }
1155
1156 if (redirection->flags & LB_PASSWORD)
1157 {
1158 if (!redirection->Password || (redirection->PasswordLength == 0))
1159 flags |= LB_PASSWORD;
1160 }
1161
1162 if (redirection->flags & LB_TARGET_FQDN)
1163 {
1164 if (utils_str_is_empty(redirection->TargetFQDN))
1165 flags |= LB_TARGET_FQDN;
1166 }
1167
1168 if (redirection->flags & LB_LOAD_BALANCE_INFO)
1169 {
1170 if (!redirection->LoadBalanceInfo || (redirection->LoadBalanceInfoLength == 0))
1171 flags |= LB_LOAD_BALANCE_INFO;
1172 }
1173
1174 if (redirection->flags & LB_TARGET_NETBIOS_NAME)
1175 {
1176 if (utils_str_is_empty(redirection->TargetNetBiosName))
1177 flags |= LB_TARGET_NETBIOS_NAME;
1178 }
1179
1180 if (redirection->flags & LB_TARGET_NET_ADDRESS)
1181 {
1182 if (utils_str_is_empty(redirection->TargetNetAddress))
1183 flags |= LB_TARGET_NET_ADDRESS;
1184 }
1185
1186 if (redirection->flags & LB_TARGET_NET_ADDRESSES)
1187 {
1188 if (!redirection->TargetNetAddresses || (redirection->TargetNetAddressesCount == 0))
1189 flags |= LB_TARGET_NET_ADDRESSES;
1190 else
1191 {
1192 for (UINT32 x = 0; x < redirection->TargetNetAddressesCount; x++)
1193 {
1194 if (!redirection->TargetNetAddresses[x])
1195 flags |= LB_TARGET_NET_ADDRESSES;
1196 }
1197 }
1198 }
1199
1200 if (redirection->flags & LB_REDIRECTION_GUID)
1201 {
1202 if (!redirection->RedirectionGuid || (redirection->RedirectionGuidLength == 0))
1203 flags |= LB_REDIRECTION_GUID;
1204 }
1205
1206 if (redirection->flags & LB_TARGET_CERTIFICATE)
1207 {
1208 if (!redirection->TargetCertificate)
1209 flags |= LB_TARGET_CERTIFICATE;
1210 }
1211
1212 if (pFlags)
1213 *pFlags = flags;
1214 return flags == 0;
1215}
1216
1217BOOL redirection_set_flags(rdpRedirection* redirection, UINT32 flags)
1218{
1219 WINPR_ASSERT(redirection);
1220 redirection->flags = flags;
1221 return TRUE;
1222}
1223
1224BOOL redirection_set_session_id(rdpRedirection* redirection, UINT32 session_id)
1225{
1226 WINPR_ASSERT(redirection);
1227 redirection->sessionID = session_id;
1228 return TRUE;
1229}
1230
1231static BOOL redirection_unsupported(const char* fkt, UINT32 flag, UINT32 mask)
1232{
1233 char buffer[1024] = WINPR_C_ARRAY_INIT;
1234 char buffer2[1024] = WINPR_C_ARRAY_INIT;
1235 WLog_WARN(TAG, "[%s] supported flags are {%s}, have {%s}", fkt,
1236 rdp_redirection_flags_to_string(mask, buffer, sizeof(buffer)),
1237 rdp_redirection_flags_to_string(flag, buffer2, sizeof(buffer2)));
1238 return FALSE;
1239}
1240
1241BOOL redirection_set_byte_option(rdpRedirection* redirection, UINT32 flag, const BYTE* data,
1242 size_t length)
1243{
1244 WINPR_ASSERT(redirection);
1245 switch (flag)
1246 {
1247 case LB_CLIENT_TSV_URL:
1248 return redirection_copy_data(&redirection->TsvUrl, &redirection->TsvUrlLength, data,
1249 length);
1250 case LB_PASSWORD:
1251 return redirection_copy_data(&redirection->Password, &redirection->PasswordLength, data,
1252 length);
1253 case LB_LOAD_BALANCE_INFO:
1254 return redirection_copy_data(&redirection->LoadBalanceInfo,
1255 &redirection->LoadBalanceInfoLength, data, length);
1256 case LB_REDIRECTION_GUID:
1257 return redirection_copy_data(&redirection->RedirectionGuid,
1258 &redirection->RedirectionGuidLength, data, length);
1259 case LB_TARGET_CERTIFICATE:
1260 return rdp_redirection_read_target_cert(&redirection->TargetCertificate, data, length);
1261 default:
1262 return redirection_unsupported(__func__, flag,
1263 LB_CLIENT_TSV_URL | LB_PASSWORD | LB_LOAD_BALANCE_INFO |
1264 LB_REDIRECTION_GUID | LB_TARGET_CERTIFICATE);
1265 }
1266}
1267
1268BOOL redirection_set_string_option(rdpRedirection* redirection, UINT32 flag, const char* str)
1269{
1270 WINPR_ASSERT(redirection);
1271 switch (flag)
1272 {
1273 case LB_USERNAME:
1274 return redirection_copy_string(&redirection->Username, str);
1275 case LB_DOMAIN:
1276 return redirection_copy_string(&redirection->Domain, str);
1277 case LB_TARGET_FQDN:
1278 return redirection_copy_string(&redirection->TargetFQDN, str);
1279 case LB_TARGET_NETBIOS_NAME:
1280 return redirection_copy_string(&redirection->TargetNetBiosName, str);
1281 case LB_TARGET_NET_ADDRESS:
1282 return redirection_copy_string(&redirection->TargetNetAddress, str);
1283 default:
1284 return redirection_unsupported(__func__, flag,
1285 LB_USERNAME | LB_DOMAIN | LB_TARGET_FQDN |
1286 LB_TARGET_NETBIOS_NAME | LB_TARGET_NET_ADDRESS);
1287 }
1288}
1289
1290BOOL redirection_set_array_option(rdpRedirection* redirection, UINT32 flag, const char** str,
1291 size_t count)
1292{
1293 WINPR_ASSERT(redirection);
1294 switch (flag)
1295 {
1296 case LB_TARGET_NET_ADDRESSES:
1297 return redirection_copy_array(&redirection->TargetNetAddresses,
1298 &redirection->TargetNetAddressesCount, str, count);
1299 default:
1300 return redirection_unsupported(__func__, flag, LB_TARGET_NET_ADDRESSES);
1301 }
1302}
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 BOOL freerdp_settings_set_pointer(rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id, const void *val)
Set a pointer to value val.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_string_len(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *val, size_t len)
Sets a string settings value. The val is copied.
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.
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.