FreeRDP
Loading...
Searching...
No Matches
assistance.c
1
20#include <freerdp/config.h>
21
22#include <errno.h>
23
24#include <winpr/wtypes.h>
25#include <winpr/collections.h>
26#include <winpr/string.h>
27#include <winpr/crt.h>
28#include <winpr/crypto.h>
29#include <winpr/print.h>
30#include <winpr/windows.h>
31#include <winpr/ssl.h>
32#include <winpr/file.h>
33
34#include <freerdp/log.h>
35#include <freerdp/client/file.h>
36#include <freerdp/client/cmdline.h>
37
38#include <freerdp/assistance.h>
39
40#include "../core/settings.h"
41
42#define TAG FREERDP_TAG("common")
43
44struct rdp_assistance_file
45{
46 UINT32 Type;
47
48 char* Username;
49 char* LHTicket;
50 char* RCTicket;
51 char* PassStub;
52 UINT32 DtStart;
53 UINT32 DtLength;
54 BOOL LowSpeed;
55 BOOL RCTicketEncrypted;
56
57 char* ConnectionString1;
58 char* ConnectionString2;
59
60 BYTE* EncryptedPassStub;
61 size_t EncryptedPassStubLength;
62
63 BYTE* EncryptedLHTicket;
64 size_t EncryptedLHTicketLength;
65
66 wArrayList* MachineAddresses;
67 wArrayList* MachinePorts;
68 wArrayList* MachineUris;
69
70 char* RASessionId;
71 char* RASpecificParams;
72 char* RASpecificParams2;
73
74 char* filename;
75 char* password;
76};
77
78static const char* strrstr(const char* haystack, size_t len, const char* needle)
79{
80 if (*needle == '\0')
81 return haystack;
82
83 char* result = nullptr;
84 for (;;)
85 {
86 char* p = strstr(haystack, needle);
87 if (p == nullptr)
88 break;
89 if (p > haystack + len)
90 return nullptr;
91
92 result = p;
93 haystack = p + 1;
94 }
95
96 return result;
97}
98
99static BOOL update_option(char** opt, const char* val, size_t len)
100{
101 WINPR_ASSERT(opt);
102 free(*opt);
103 *opt = nullptr;
104
105 if (!val && (len != 0))
106 return FALSE;
107 else if (!val && (len == 0))
108 return TRUE;
109 *opt = strndup(val, len);
110 return *opt != nullptr;
111}
112
113static BOOL update_name(rdpAssistanceFile* file, const char* name)
114{
115 WINPR_ASSERT(file);
116
117 if (!name)
118 {
119 WLog_ERR(TAG, "ASSISTANCE file %s invalid name", name);
120 return FALSE;
121 }
122
123 free(file->filename);
124 file->filename = _strdup(name);
125 return file->filename != nullptr;
126}
127
128static BOOL update_password(rdpAssistanceFile* file, const char* password)
129{
130 WINPR_ASSERT(file);
131 free(file->password);
132 file->password = nullptr;
133 if (!password)
134 return TRUE;
135 file->password = _strdup(password);
136 return file->password != nullptr;
137}
138
139static BOOL update_connectionstring2_nocopy(rdpAssistanceFile* file, char* str)
140{
141 WINPR_ASSERT(file);
142 free(file->ConnectionString2);
143 file->ConnectionString2 = nullptr;
144 if (!str)
145 return TRUE;
146 file->ConnectionString2 = str;
147 return file->ConnectionString2 != nullptr;
148}
149
150static BOOL update_connectionstring2(rdpAssistanceFile* file, const char* str, size_t len)
151{
152 char* strc = nullptr;
153 if (!str && (len != 0))
154 return FALSE;
155
156 if (str && (len > 0))
157 {
158 strc = strndup(str, len);
159 if (!strc)
160 return FALSE;
161 }
162 return update_connectionstring2_nocopy(file, strc);
163}
164
165static BOOL update_connectionstring2_wchar(rdpAssistanceFile* file, const WCHAR* str, size_t len)
166{
167 char* strc = nullptr;
168
169 if (!str && (len != 0))
170 return FALSE;
171
172 if (str && (len > 0))
173 {
174 strc = ConvertWCharNToUtf8Alloc(str, len, nullptr);
175 if (!strc)
176 return FALSE;
177 }
178 return update_connectionstring2_nocopy(file, strc);
179}
180
217static BOOL freerdp_assistance_crypt_derive_key_sha1(const BYTE* hash, size_t hashLength, BYTE* key,
218 size_t keyLength)
219{
220 BOOL rc = FALSE;
221 BYTE pad1[64] = WINPR_C_ARRAY_INIT;
222 BYTE pad2[64] = WINPR_C_ARRAY_INIT;
223
224 if (hashLength == 0)
225 return FALSE;
226
227 memset(pad1, 0x36, sizeof(pad1));
228 memset(pad2, 0x5C, sizeof(pad2));
229
230 for (size_t i = 0; i < hashLength; i++)
231 {
232 pad1[i] ^= hash[i];
233 pad2[i] ^= hash[i];
234 }
235
236 BYTE* buffer = (BYTE*)calloc(hashLength, 2);
237
238 if (!buffer)
239 goto fail;
240
241 if (!winpr_Digest(WINPR_MD_SHA1, pad1, 64, buffer, hashLength))
242 goto fail;
243
244 if (!winpr_Digest(WINPR_MD_SHA1, pad2, 64, &buffer[hashLength], hashLength))
245 goto fail;
246
247 CopyMemory(key, buffer, keyLength);
248 rc = TRUE;
249fail:
250 free(buffer);
251 return rc;
252}
253
254static BOOL append_address_to_list(wArrayList* MachineAddresses, const char* str, size_t len)
255{
256 char* copy = nullptr;
257 if (len > 0)
258 copy = strndup(str, len);
259 if (!copy)
260 return FALSE;
261
262 const BOOL rc = ArrayList_Append(MachineAddresses, copy);
263 if (!rc)
264 free(copy);
265 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership of copy
266 return rc;
267}
268
269static BOOL append_address(rdpAssistanceFile* file, const char* host, const char* port)
270{
271 WINPR_ASSERT(file);
272
273 errno = 0;
274 unsigned long p = strtoul(port, nullptr, 0);
275
276 if ((errno != 0) || (p == 0) || (p > UINT16_MAX))
277 {
278 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid port value %s",
279 port);
280 return FALSE;
281 }
282
283 if (!append_address_to_list(file->MachineAddresses, host, host ? strlen(host) : 0))
284 return FALSE;
285 return ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p);
286}
287
288static BOOL freerdp_assistance_parse_address_list(rdpAssistanceFile* file, char* list)
289{
290 WINPR_ASSERT(file);
291
292 WLog_DBG(TAG, "freerdp_assistance_parse_address_list list=%s", list);
293
294 BOOL rc = FALSE;
295
296 if (!list)
297 return FALSE;
298
299 char* strp = list;
300 char* s = ";";
301
302 // get the first token
303 char* saveptr = nullptr;
304 char* token = strtok_s(strp, s, &saveptr);
305
306 // walk through other tokens
307 while (token != nullptr)
308 {
309 char* port = strchr(token, ':');
310 if (!port)
311 goto out;
312 *port = '\0';
313 port++;
314
315 if (!append_address(file, token, port))
316 goto out;
317
318 token = strtok_s(nullptr, s, &saveptr);
319 }
320 rc = TRUE;
321out:
322 return rc;
323}
324
325static BOOL freerdp_assistance_parse_connection_string1(rdpAssistanceFile* file)
326{
327 char* tokens[8] = WINPR_C_ARRAY_INIT;
328 BOOL rc = FALSE;
329
330 WINPR_ASSERT(file);
331
332 if (!file->RCTicket)
333 return FALSE;
334
339 char* str = _strdup(file->RCTicket);
340
341 if (!str)
342 goto error;
343
344 {
345 const size_t length = strlen(str);
346
347 {
348 int count = 1;
349 for (size_t i = 0; i < length; i++)
350 {
351 if (str[i] == ',')
352 count++;
353 }
354
355 if (count != 8)
356 goto error;
357 }
358
359 {
360 size_t count = 0;
361 tokens[count++] = str;
362
363 for (size_t i = 0; i < length; i++)
364 {
365 if (str[i] == ',')
366 {
367 str[i] = '\0';
368 tokens[count++] = &str[i + 1];
369 }
370 }
371 }
372 }
373
374 if (strcmp(tokens[0], "65538") != 0)
375 goto error;
376
377 if (strcmp(tokens[1], "1") != 0)
378 goto error;
379
380 if (strcmp(tokens[3], "*") != 0)
381 goto error;
382
383 if (strcmp(tokens[5], "*") != 0)
384 goto error;
385
386 if (strcmp(tokens[6], "*") != 0)
387 goto error;
388
389 file->RASessionId = _strdup(tokens[4]);
390
391 if (!file->RASessionId)
392 goto error;
393
394 file->RASpecificParams = _strdup(tokens[7]);
395
396 if (!file->RASpecificParams)
397 goto error;
398
399 if (!freerdp_assistance_parse_address_list(file, tokens[2]))
400 goto error;
401
402 rc = TRUE;
403error:
404 free(str);
405 return rc;
406}
407
421static BOOL freerdp_assistance_parse_attr(const char** opt, size_t* plength, const char* key,
422 const char* tag)
423{
424 WINPR_ASSERT(opt);
425 WINPR_ASSERT(plength);
426 WINPR_ASSERT(key);
427
428 *opt = nullptr;
429 *plength = 0;
430 if (!tag)
431 return FALSE;
432
433 char bkey[128] = WINPR_C_ARRAY_INIT;
434 const int rc = _snprintf(bkey, sizeof(bkey), "%s=\"", key);
435 WINPR_ASSERT(rc > 0);
436 WINPR_ASSERT((size_t)rc < sizeof(bkey));
437 if ((rc <= 0) || ((size_t)rc >= sizeof(bkey)))
438 return FALSE;
439
440 char* p = strstr(tag, bkey);
441 if (!p)
442 return TRUE;
443
444 p += strlen(bkey);
445 char* q = strchr(p, '"');
446
447 if (!q)
448 {
449 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 invalid field '%s=%s'",
450 key, p);
451 return FALSE;
452 }
453
454 if (p > q)
455 {
456 WLog_ERR(TAG,
457 "Failed to parse ASSISTANCE file: ConnectionString2 invalid field "
458 "order for '%s'",
459 key);
460 return FALSE;
461 }
462 const size_t length = WINPR_ASSERTING_INT_CAST(size_t, q - p);
463 *opt = p;
464 *plength = length;
465
466 return TRUE;
467}
468
469static BOOL freerdp_assistance_parse_attr_str(char** opt, const char* key, const char* tag)
470{
471 const char* copt = nullptr;
472 size_t size = 0;
473 if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
474 return FALSE;
475 return update_option(opt, copt, size);
476}
477
478static BOOL freerdp_assistance_parse_attr_bool(BOOL* opt, const char* key, const char* tag)
479{
480 const char* copt = nullptr;
481 size_t size = 0;
482
483 WINPR_ASSERT(opt);
484 *opt = FALSE;
485
486 if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
487 return FALSE;
488 if (size != 1)
489 return TRUE;
490
491 *opt = (copt[0] == '1');
492 return TRUE;
493}
494
495static BOOL freerdp_assistance_parse_attr_uint32(UINT32* opt, const char* key, const char* tag)
496{
497 const char* copt = nullptr;
498 size_t size = 0;
499
500 WINPR_ASSERT(opt);
501 *opt = 0;
502
503 if (!freerdp_assistance_parse_attr(&copt, &size, key, tag))
504 return FALSE;
505
506 char buffer[64] = WINPR_C_ARRAY_INIT;
507 if ((!copt && (size > 0)) || (size >= sizeof(buffer)))
508 {
509 WLog_WARN(TAG, "Invalid UINT32 string '%s' [%" PRIuz "]", copt, size);
510 return FALSE;
511 }
512
513 if (size > 0)
514 strncpy(buffer, copt, size);
515
516 errno = 0;
517 unsigned long val = strtoul(buffer, nullptr, 0);
518
519 if ((errno != 0) || (val > UINT32_MAX))
520 {
521 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Invalid value %s", buffer);
522 return FALSE;
523 }
524
525 *opt = (UINT32)val;
526
527 return TRUE;
528}
529
530static char* freerdp_assistance_contains_element(char* input, size_t ilen, const char* key,
531 size_t* plen, char** pdata, size_t* pdlen)
532{
533 WINPR_ASSERT(input);
534 WINPR_ASSERT(key);
535 WINPR_ASSERT(plen);
536
537 char bkey[128] = WINPR_C_ARRAY_INIT;
538 const int rc = _snprintf(bkey, sizeof(bkey), "<%s", key);
539 WINPR_ASSERT(rc > 0);
540 WINPR_ASSERT((size_t)rc < sizeof(bkey));
541 if ((rc < 0) || ((size_t)rc >= sizeof(bkey)))
542 return nullptr;
543
544 char* tag = strstr(input, bkey);
545 if (!tag || (tag > input + ilen))
546 return nullptr;
547
548 char* data = tag + strnlen(bkey, sizeof(bkey));
549
550 /* Ensure there is a valid delimiter following our token */
551 switch (data[0])
552 {
553 case '>':
554 case '/':
555 case ' ':
556 case '\t':
557 break;
558 default:
559 WLog_ERR(TAG,
560 "Failed to parse ASSISTANCE file: ConnectionString2 missing delimiter after "
561 "field %s",
562 bkey);
563 return nullptr;
564 }
565
566 char* start = strstr(tag, ">");
567
568 if (!start || (start > input + ilen))
569 {
570 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: ConnectionString2 missing field %s", bkey);
571 return nullptr;
572 }
573
574 const char* end = start;
575 const char* dend = start - 1;
576 if (*dend != '/')
577 {
578 char ekey[128] = WINPR_C_ARRAY_INIT;
579 const int erc = _snprintf(ekey, sizeof(ekey), "</%s>", key);
580 WINPR_ASSERT(erc > 0);
581 WINPR_ASSERT((size_t)erc < sizeof(ekey));
582 if ((erc <= 0) || ((size_t)erc >= sizeof(ekey)))
583 return nullptr;
584 const size_t offset = WINPR_ASSERTING_INT_CAST(size_t, start - tag);
585 dend = end = strrstr(start, ilen - offset, ekey);
586 if (end)
587 end += strnlen(ekey, sizeof(ekey));
588 }
589
590 if (!end)
591 {
592 WLog_ERR(TAG,
593 "Failed to parse ASSISTANCE file: ConnectionString2 missing end tag for field %s",
594 key);
595 return nullptr;
596 }
597 if (plen)
598 *plen = WINPR_ASSERTING_INT_CAST(size_t, end - tag);
599
600 if (pdata)
601 *pdata = data;
602 if (pdlen)
603 *pdlen = WINPR_ASSERTING_INT_CAST(size_t, dend - data);
604 return tag;
605}
606
612static BOOL freerdp_assistance_consume_input_and_get_element(char* input, const char* key,
613 char** element, size_t* elen)
614{
615 WINPR_ASSERT(input);
616 WINPR_ASSERT(key);
617 WINPR_ASSERT(element);
618 WINPR_ASSERT(elen);
619
620 size_t len = 0;
621 size_t dlen = 0;
622 char* data = nullptr;
623 char* tag = freerdp_assistance_contains_element(input, strlen(input), key, &len, &data, &dlen);
624 if (!tag)
625 return FALSE;
626
627 char* end = data + dlen;
628 *tag = '\0';
629 *end = '\0';
630 *element = data;
631 *elen = dlen + 1;
632 return TRUE;
633}
634
635static BOOL freerdp_assistance_get_element(char* input, size_t ilen, const char* key,
636 char** element, size_t* elen)
637{
638 WINPR_ASSERT(input);
639 WINPR_ASSERT(key);
640 WINPR_ASSERT(element);
641 WINPR_ASSERT(elen);
642
643 size_t len = 0;
644 size_t dlen = 0;
645 char* data = nullptr;
646 char* tag = freerdp_assistance_contains_element(input, ilen, key, &len, &data, &dlen);
647 if (!tag)
648 return FALSE;
649
650 if (tag + len > input + ilen)
651 return FALSE;
652
653 char* end = tag + len;
654 *element = data;
655 *elen = WINPR_ASSERTING_INT_CAST(size_t, end - data + 1);
656 return TRUE;
657}
658
659static BOOL freerdp_assistance_parse_all_elements_of(rdpAssistanceFile* file, char* data,
660 size_t len, const char* key,
661 BOOL (*fkt)(rdpAssistanceFile* file,
662 char* data, size_t len))
663{
664 char* val = nullptr;
665 size_t vlen = 0;
666
667 while (freerdp_assistance_get_element(data, len, key, &val, &vlen))
668 {
669 data = val + vlen;
670 len = strnlen(data, len);
671 if (vlen > 0)
672 {
673 val[vlen - 1] = '\0';
674
675 if (!fkt(file, val, vlen))
676 return FALSE;
677 }
678 }
679
680 return TRUE;
681}
682
683static BOOL freerdp_assistance_parse_all_elements_of_l(rdpAssistanceFile* file, char* data,
684 WINPR_ATTR_UNUSED size_t len)
685{
686 UINT32 p = 0;
687 const char* n = nullptr;
688 const char* u = nullptr;
689 size_t nlen = 0;
690 size_t ulen = 0;
691 if (!freerdp_assistance_parse_attr_uint32(&p, "P", data))
692 return FALSE;
693 if (!freerdp_assistance_parse_attr(&n, &nlen, "N", data))
694 return FALSE;
695 if (!freerdp_assistance_parse_attr(&u, &ulen, "U", data))
696 return FALSE;
697
698 if (n && (nlen > 0))
699 {
700 if (!append_address_to_list(file->MachineAddresses, n, nlen))
701 return FALSE;
702 if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p))
703 return FALSE;
704 }
705 if (u && (ulen > 0))
706 {
707 if (!append_address_to_list(file->MachineAddresses, u, ulen))
708 return FALSE;
709 if (!ArrayList_Append(file->MachinePorts, (void*)(uintptr_t)p))
710 return FALSE;
711 }
712 return TRUE;
713}
714
715static BOOL freerdp_assistance_parse_all_elements_of_t(rdpAssistanceFile* file, char* data,
716 size_t len)
717{
718 UINT32 id = 0;
719 UINT32 sid = 0;
720 if (!freerdp_assistance_parse_attr_uint32(&id, "ID", data))
721 return FALSE;
722 if (!freerdp_assistance_parse_attr_uint32(&sid, "SID", data))
723 return FALSE;
724 WLog_DBG(TAG, "transport id=%" PRIu32 ", sid=%" PRIu32, id, sid);
725 return freerdp_assistance_parse_all_elements_of(file, data, len, "L",
726 freerdp_assistance_parse_all_elements_of_l);
727}
728
729static BOOL freerdp_assistance_parse_all_elements_of_c(rdpAssistanceFile* file, char* data,
730 size_t len)
731{
732 return freerdp_assistance_parse_all_elements_of(file, data, len, "T",
733 freerdp_assistance_parse_all_elements_of_t);
734}
735
736static BOOL freerdp_assistance_parse_find_elements_of_c(rdpAssistanceFile* file, char* data,
737 size_t len)
738{
739 return freerdp_assistance_parse_all_elements_of(file, data, len, "C",
740 freerdp_assistance_parse_all_elements_of_c);
741}
742
743static BOOL freerdp_assistance_parse_connection_string2(rdpAssistanceFile* file)
744{
745 BOOL rc = FALSE;
746
747 WINPR_ASSERT(file);
748
749 if (!file->ConnectionString2)
750 return FALSE;
751
752 char* str = _strdup(file->ConnectionString2);
753 if (!str)
754 goto out_fail;
755
756 {
757 char* e = nullptr;
758 size_t elen = 0;
759 if (!freerdp_assistance_consume_input_and_get_element(str, "E", &e, &elen))
760 goto out_fail;
761
762 if (!e || (elen == 0))
763 goto out_fail;
764 {
765 char* a = nullptr;
766 size_t alen = 0;
767 if (!freerdp_assistance_get_element(e, elen, "A", &a, &alen))
768 goto out_fail;
769
770 if (!a || (alen == 0))
771 goto out_fail;
772
773 if (!freerdp_assistance_parse_find_elements_of_c(file, e, elen))
774 goto out_fail;
775
776 /* '\0' terminate the detected XML elements so
777 * the parser can continue with terminated strings
778 */
779 a[alen] = '\0';
780
781 if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams, "KH", a))
782 goto out_fail;
783
784 if (!freerdp_assistance_parse_attr_str(&file->RASpecificParams2, "KH2", a))
785 goto out_fail;
786
787 if (!freerdp_assistance_parse_attr_str(&file->RASessionId, "ID", a))
788 goto out_fail;
789 }
790 }
791 rc = TRUE;
792out_fail:
793 free(str);
794 return rc;
795}
796
797char* freerdp_assistance_construct_expert_blob(const char* name, const char* pass)
798{
799 if (!name || !pass)
800 return nullptr;
801
802 const size_t nameLength = strlen(name) + strlen("NAME=");
803 const size_t passLength = strlen(pass) + strlen("PASS=");
804 const size_t size = nameLength + passLength + 64;
805 char* ExpertBlob = (char*)calloc(1, size);
806
807 if (!ExpertBlob)
808 return nullptr;
809
810 (void)sprintf_s(ExpertBlob, size, "%" PRIuz ";NAME=%s%" PRIuz ";PASS=%s", nameLength, name,
811 passLength, pass);
812 return ExpertBlob;
813}
814
815char* freerdp_assistance_generate_pass_stub(WINPR_ATTR_UNUSED DWORD flags)
816{
817 UINT32 nums[14] = WINPR_C_ARRAY_INIT;
818 const char set1[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
819 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
820 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
821 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
822 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '_' };
823 const char set2[12] = { '!', '@', '#', '$', '&', '^', '*', '(', ')', '-', '+', '=' };
824 const char set3[10] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
825 const char set4[26] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
826 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
827 const char set5[26] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
828 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
829 char* passStub = calloc(15, sizeof(char));
830
831 if (!passStub)
832 return nullptr;
833
845 if (winpr_RAND(nums, sizeof(nums)) < 0)
846 {
847 free(passStub);
848 return nullptr;
849 }
850
851 passStub[0] = set1[nums[0] % sizeof(set1)]; /* character 0 */
852 passStub[1] = set2[nums[1] % sizeof(set2)]; /* character 1 */
853 passStub[2] = set3[nums[2] % sizeof(set3)]; /* character 2 */
854 passStub[3] = set4[nums[3] % sizeof(set4)]; /* character 3 */
855 passStub[4] = set5[nums[4] % sizeof(set5)]; /* character 4 */
856
857 for (size_t x = 5; x < ARRAYSIZE(nums); x++)
858 passStub[x] = set1[nums[x] % sizeof(set1)]; /* character 5 - 13 */
859 return passStub;
860}
861
862BYTE* freerdp_assistance_encrypt_pass_stub(const char* password, const char* passStub,
863 size_t* pEncryptedSize)
864{
865 BOOL rc = 0;
866 size_t cbPasswordW = 0;
867 size_t cbPassStubW = 0;
868 BYTE PasswordHash[WINPR_MD5_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
869 WINPR_RC4_CTX* rc4Ctx = nullptr;
870 BYTE* pbIn = nullptr;
871 BYTE* pbOut = nullptr;
872 BYTE* res = nullptr;
873 WCHAR* PasswordW = ConvertUtf8ToWCharAlloc(password, &cbPasswordW);
874 WCHAR* PassStubW = ConvertUtf8ToWCharAlloc(passStub, &cbPassStubW);
875
876 cbPasswordW = cbPasswordW * sizeof(WCHAR);
877 cbPassStubW = cbPassStubW * sizeof(WCHAR);
878 const size_t EncryptedSize = cbPassStubW + 4;
879
880 if (!PasswordW || !PassStubW)
881 goto fail;
882
883 if (!winpr_Digest(WINPR_MD_MD5, (BYTE*)PasswordW, cbPasswordW, (BYTE*)PasswordHash,
884 sizeof(PasswordHash)))
885 goto fail;
886
887 pbIn = (BYTE*)calloc(1, EncryptedSize);
888 pbOut = (BYTE*)calloc(1, EncryptedSize);
889
890 if (!pbIn || !pbOut)
891 goto fail;
892
893 WINPR_ASSERT(cbPasswordW <= UINT32_MAX);
894 winpr_Data_Write_UINT32(pbIn, (UINT32)cbPassStubW);
895 CopyMemory(&pbIn[4], PassStubW, cbPassStubW);
896 rc4Ctx = winpr_RC4_New(PasswordHash, sizeof(PasswordHash));
897
898 if (!rc4Ctx)
899 {
900 WLog_ERR(TAG, "winpr_Cipher_New failure");
901 goto fail;
902 }
903
904 rc = winpr_RC4_Update(rc4Ctx, EncryptedSize, pbIn, pbOut);
905
906 if (!rc)
907 {
908 WLog_ERR(TAG, "winpr_Cipher_Update failure");
909 goto fail;
910 }
911 res = pbOut;
912fail:
913 winpr_RC4_Free(rc4Ctx);
914 free(PasswordW);
915 free(PassStubW);
916 free(pbIn);
917 if (!res)
918 free(pbOut);
919 else
920 *pEncryptedSize = EncryptedSize;
921 return res;
922}
923
924static BOOL freerdp_assistance_decrypt2(rdpAssistanceFile* file)
925{
926 BOOL rc = FALSE;
927 int status = 0;
928 size_t cbPasswordW = 0;
929 size_t cchOutW = 0;
930 WINPR_CIPHER_CTX* aesDec = nullptr;
931 WCHAR* PasswordW = nullptr;
932 BYTE* pbIn = nullptr;
933 BYTE* pbOut = nullptr;
934 size_t cbOut = 0;
935 size_t cbIn = 0;
936 size_t cbFinal = 0;
937 BYTE DerivedKey[WINPR_AES_BLOCK_SIZE] = WINPR_C_ARRAY_INIT;
938 BYTE InitializationVector[WINPR_AES_BLOCK_SIZE] = WINPR_C_ARRAY_INIT;
939 BYTE PasswordHash[WINPR_SHA1_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
940
941 WINPR_ASSERT(file);
942
943 if (!file->password)
944 return FALSE;
945
946 PasswordW = ConvertUtf8ToWCharAlloc(file->password, &cbPasswordW);
947 if (!PasswordW)
948 {
949 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed");
950 return FALSE;
951 }
952
953 cbPasswordW = cbPasswordW * sizeof(WCHAR);
954
955 if (!winpr_Digest(WINPR_MD_SHA1, (BYTE*)PasswordW, cbPasswordW, PasswordHash,
956 sizeof(PasswordHash)))
957 goto fail;
958
959 if (!freerdp_assistance_crypt_derive_key_sha1(PasswordHash, sizeof(PasswordHash), DerivedKey,
960 sizeof(DerivedKey)))
961 goto fail;
962
963 aesDec =
964 winpr_Cipher_NewEx(WINPR_CIPHER_AES_128_CBC, WINPR_DECRYPT, DerivedKey, sizeof(DerivedKey),
965 InitializationVector, sizeof(InitializationVector));
966
967 if (!aesDec)
968 goto fail;
969
970 cbOut = cbFinal = 0;
971 cbIn = file->EncryptedLHTicketLength;
972 pbIn = file->EncryptedLHTicket;
973 pbOut = (BYTE*)calloc(1, cbIn + WINPR_AES_BLOCK_SIZE + 2);
974
975 if (!pbOut)
976 goto fail;
977
978 if (!winpr_Cipher_Update(aesDec, pbIn, cbIn, pbOut, &cbOut))
979 goto fail;
980
981 if (!winpr_Cipher_Final(aesDec, pbOut + cbOut, &cbFinal))
982 {
983 WLog_ERR(TAG, "winpr_Cipher_Final failure");
984 goto fail;
985 }
986
987 cbOut += cbFinal;
988 cbFinal = 0;
989
990 union
991 {
992 const WCHAR* wc;
993 const BYTE* b;
994 } cnv;
995
996 cnv.b = pbOut;
997 cchOutW = cbOut / sizeof(WCHAR);
998
999 if (!update_connectionstring2_wchar(file, cnv.wc, cchOutW))
1000 {
1001 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Conversion from UCS2 to UTF8 failed");
1002 goto fail;
1003 }
1004
1005 if (!freerdp_assistance_parse_connection_string2(file))
1006 goto fail;
1007
1008 rc = TRUE;
1009fail:
1010 winpr_Cipher_Free(aesDec);
1011 free(PasswordW);
1012 free(pbOut);
1013 WLog_DBG(TAG, "freerdp_assistance_parse_connection_string2: %d", status);
1014 return rc;
1015}
1016
1017BYTE* freerdp_assistance_hex_string_to_bin(const void* str, size_t* size)
1018{
1019 BYTE* buffer = nullptr;
1020 if (!str || !size)
1021 return nullptr;
1022 *size = 0;
1023 const size_t length = strlen(str);
1024 buffer = calloc(length, sizeof(BYTE));
1025 if (!buffer)
1026 return nullptr;
1027 const size_t rc = winpr_HexStringToBinBuffer(str, length, buffer, length);
1028 if (rc == 0)
1029 {
1030 free(buffer);
1031 return nullptr;
1032 }
1033 *size = rc;
1034 return buffer;
1035}
1036
1037char* freerdp_assistance_bin_to_hex_string(const void* data, size_t size)
1038{
1039 return winpr_BinToHexString(data, size, FALSE);
1040}
1041
1042static int freerdp_assistance_parse_uploadinfo(rdpAssistanceFile* file, char* uploadinfo,
1043 size_t uploadinfosize)
1044{
1045 const char escalated[9] = { 'E', 's', 'c', 'a', 'l', 'a', 't', 'e', 'd' };
1046 const size_t esclen = sizeof(escalated);
1047 const char* typestr = nullptr;
1048 size_t typelen = 0;
1049
1050 if (!uploadinfo || (uploadinfosize == 0))
1051 return -1;
1052
1053 if (strnlen(uploadinfo, uploadinfosize) == uploadinfosize)
1054 {
1055 WLog_WARN(TAG, "UPLOADINFOR string is not '\\0' terminated");
1056 return -1;
1057 }
1058
1059 if (!freerdp_assistance_parse_attr(&typestr, &typelen, "TYPE", uploadinfo))
1060 return -1;
1061
1062 if ((typelen != esclen) || (strncmp(typestr, escalated, esclen) != 0))
1063 {
1064 WLog_ERR(TAG,
1065 "Failed to parse ASSISTANCE file: Missing or invalid UPLOADINFO TYPE '%s' [%" PRIuz
1066 "]",
1067 typestr, typelen);
1068 return -1;
1069 }
1070
1071 char* uploaddata = nullptr;
1072 size_t uploaddatasize = 0;
1073 if (!freerdp_assistance_consume_input_and_get_element(uploadinfo, "UPLOADDATA", &uploaddata,
1074 &uploaddatasize))
1075 return -1;
1076
1077 /* Parse USERNAME */
1078 if (!freerdp_assistance_parse_attr_str(&file->Username, "USERNAME", uploaddata))
1079 return -1;
1080
1081 /* Parse LHTICKET */
1082 if (!freerdp_assistance_parse_attr_str(&file->LHTicket, "LHTICKET", uploaddata))
1083 return -1;
1084
1085 /* Parse RCTICKET */
1086 if (!freerdp_assistance_parse_attr_str(&file->RCTicket, "RCTICKET", uploaddata))
1087 return -1;
1088
1089 /* Parse RCTICKETENCRYPTED */
1090 if (!freerdp_assistance_parse_attr_bool(&file->RCTicketEncrypted, "RCTICKETENCRYPTED",
1091 uploaddata))
1092 return -1;
1093
1094 /* Parse PassStub */
1095 if (!freerdp_assistance_parse_attr_str(&file->PassStub, "PassStub", uploaddata))
1096 return -1;
1097
1098 if (file->PassStub)
1099 {
1100 const char* amp = "&amp;";
1101 char* passtub = strstr(file->PassStub, amp);
1102 while (passtub)
1103 {
1104 const char* end = passtub + 5;
1105 const size_t len = strlen(end);
1106 memmove(&passtub[1], end, len + 1);
1107 passtub = strstr(passtub, amp);
1108 }
1109 }
1110
1111 /* Parse DtStart */
1112 if (!freerdp_assistance_parse_attr_uint32(&file->DtStart, "DtStart", uploaddata))
1113 return -1;
1114
1115 /* Parse DtLength */
1116 if (!freerdp_assistance_parse_attr_uint32(&file->DtLength, "DtLength", uploaddata))
1117 return -1;
1118
1119 /* Parse L (LowSpeed) */
1120 if (!freerdp_assistance_parse_attr_bool(&file->LowSpeed, "L", uploaddata))
1121 return -1;
1122
1123 file->Type = (file->LHTicket) ? 2 : 1;
1124 int status = 0;
1125
1126 switch (file->Type)
1127 {
1128 case 2:
1129 {
1130 file->EncryptedLHTicket = freerdp_assistance_hex_string_to_bin(
1131 file->LHTicket, &file->EncryptedLHTicketLength);
1132
1133 if (!freerdp_assistance_decrypt2(file))
1134 status = -1;
1135 }
1136 break;
1137
1138 case 1:
1139 {
1140 if (!freerdp_assistance_parse_connection_string1(file))
1141 status = -1;
1142 }
1143 break;
1144
1145 default:
1146 return -1;
1147 }
1148
1149 if (status < 0)
1150 {
1151 WLog_ERR(TAG, "freerdp_assistance_parse_connection_string1 failure: %d", status);
1152 return -1;
1153 }
1154
1155 file->EncryptedPassStub = freerdp_assistance_encrypt_pass_stub(file->password, file->PassStub,
1156 &file->EncryptedPassStubLength);
1157
1158 if (!file->EncryptedPassStub)
1159 return -1;
1160
1161 return 1;
1162}
1163
1164static int freerdp_assistance_parse_file_buffer_int(rdpAssistanceFile* file, char* buffer,
1165 size_t size, const char* password)
1166{
1167 WINPR_ASSERT(file);
1168 WINPR_ASSERT(buffer);
1169 WINPR_ASSERT(size > 0);
1170
1171 if (!update_password(file, password))
1172 return -1;
1173
1174 char* uploadinfo = nullptr;
1175 size_t uploadinfosize = 0;
1176 if (freerdp_assistance_consume_input_and_get_element(buffer, "UPLOADINFO", &uploadinfo,
1177 &uploadinfosize))
1178 return freerdp_assistance_parse_uploadinfo(file, uploadinfo, uploadinfosize);
1179
1180 size_t elen = 0;
1181 const char* estr =
1182 freerdp_assistance_contains_element(buffer, size, "E", &elen, nullptr, nullptr);
1183 if (!estr || (elen == 0))
1184 {
1185 WLog_ERR(TAG, "Failed to parse ASSISTANCE file: Neither UPLOADINFO nor <E> found");
1186 return -1;
1187 }
1188 if (!update_connectionstring2(file, estr, elen))
1189 return -1;
1190
1191 if (!freerdp_assistance_parse_connection_string2(file))
1192 return -1;
1193
1194 return 1;
1195}
1196
1197int freerdp_assistance_parse_file_buffer(rdpAssistanceFile* file, const char* cbuffer, size_t size,
1198 const char* password)
1199{
1200 WINPR_ASSERT(file);
1201 if (!password)
1202 {
1203 WLog_WARN(TAG, "empty password supplied");
1204 }
1205
1206 if (!cbuffer || (size == 0))
1207 {
1208 WLog_WARN(TAG, "no data supplied [%p, %" PRIuz "]",
1209 WINPR_CXX_COMPAT_CAST(const void*, cbuffer), size);
1210 return -1;
1211 }
1212
1213 char* abuffer = strndup(cbuffer, size);
1214 const size_t len = strnlen(cbuffer, size);
1215 if (len == size)
1216 WLog_WARN(TAG, "Input data not '\\0' terminated");
1217
1218 if (!abuffer)
1219 return -1;
1220
1221 const int rc = freerdp_assistance_parse_file_buffer_int(file, abuffer, len + 1, password);
1222 free(abuffer);
1223 return rc;
1224}
1225
1226int freerdp_assistance_parse_file(rdpAssistanceFile* file, const char* name, const char* password)
1227{
1228 int status = 0;
1229 BYTE* buffer = nullptr;
1230 FILE* fp = nullptr;
1231 size_t readSize = 0;
1232 union
1233 {
1234 INT64 i64;
1235 size_t s;
1236 } fileSize;
1237
1238 if (!update_name(file, name))
1239 return -1;
1240
1241 fp = winpr_fopen(name, "r");
1242
1243 if (!fp)
1244 {
1245 WLog_ERR(TAG, "Failed to open ASSISTANCE file %s ", name);
1246 return -1;
1247 }
1248
1249 (void)_fseeki64(fp, 0, SEEK_END);
1250 fileSize.i64 = _ftelli64(fp);
1251 (void)_fseeki64(fp, 0, SEEK_SET);
1252
1253 if (fileSize.i64 < 1)
1254 {
1255 WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name);
1256 (void)fclose(fp);
1257 return -1;
1258 }
1259
1260 buffer = (BYTE*)malloc(fileSize.s + 2);
1261
1262 if (!buffer)
1263 {
1264 (void)fclose(fp);
1265 return -1;
1266 }
1267
1268 readSize = fread(buffer, fileSize.s, 1, fp);
1269
1270 if (!readSize)
1271 {
1272 if (!ferror(fp))
1273 readSize = fileSize.s;
1274 }
1275
1276 (void)fclose(fp);
1277
1278 if (readSize < 1)
1279 {
1280 WLog_ERR(TAG, "Failed to read ASSISTANCE file %s ", name);
1281 free(buffer);
1282 buffer = nullptr;
1283 return -1;
1284 }
1285
1286 buffer[fileSize.s] = '\0';
1287 buffer[fileSize.s + 1] = '\0';
1288 status = freerdp_assistance_parse_file_buffer(file, (char*)buffer, fileSize.s, password);
1289 free(buffer);
1290 return status;
1291}
1292
1293BOOL freerdp_assistance_populate_settings_from_assistance_file(rdpAssistanceFile* file,
1294 rdpSettings* settings)
1295{
1296 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE))
1297 return FALSE;
1298
1299 if (!file->RASessionId || !file->MachineAddresses)
1300 return FALSE;
1301
1302 if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceSessionId,
1303 file->RASessionId))
1304 return FALSE;
1305
1306 if (file->RCTicket)
1307 {
1308 if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket,
1309 file->RCTicket))
1310 return FALSE;
1311 }
1312 else
1313 {
1314 if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistanceRCTicket,
1315 file->ConnectionString2))
1316 return FALSE;
1317 }
1318
1319 if (file->PassStub)
1320 {
1321 if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassStub,
1322 file->PassStub))
1323 return FALSE;
1324 }
1325
1326 if (ArrayList_Count(file->MachineAddresses) < 1)
1327 return FALSE;
1328
1329 const char* addr = ArrayList_GetItem(file->MachineAddresses, 0);
1330 if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, addr))
1331 return FALSE;
1332
1333 if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, file->filename))
1334 return FALSE;
1335
1336 if (!freerdp_settings_set_string(settings, FreeRDP_RemoteAssistancePassword, file->password))
1337 return FALSE;
1338
1339 if (file->Username)
1340 {
1341 if (!freerdp_settings_set_string(settings, FreeRDP_Username, file->Username))
1342 return FALSE;
1343 }
1344
1345 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, TRUE))
1346 return FALSE;
1347
1348 const size_t ports = ArrayList_Count(file->MachinePorts);
1349 const size_t addresses = ArrayList_Count(file->MachineAddresses);
1350 if (ports < 1)
1351 return FALSE;
1352 if (ports != addresses)
1353 return FALSE;
1354
1355 union
1356 {
1357 uintptr_t port;
1358 void* data;
1359 } cnv;
1360 cnv.data = ArrayList_GetItem(file->MachinePorts, 0);
1361 WINPR_ASSERT(cnv.port <= UINT32_MAX);
1362 if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)cnv.port))
1363 return FALSE;
1364
1365 if (!freerdp_target_net_adresses_reset(settings, ports))
1366 return FALSE;
1367
1368 for (size_t x = 0; x < ports; x++)
1369 {
1370 cnv.data = ArrayList_GetItem(file->MachinePorts, x);
1371 WINPR_ASSERT(cnv.port <= UINT32_MAX);
1372 const UINT32 port = (UINT32)cnv.port;
1373 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetPorts, x, &port))
1374 return FALSE;
1375 }
1376 for (size_t i = 0; i < addresses; i++)
1377 {
1378 const char* maddr = ArrayList_GetItem(file->MachineAddresses, i);
1379 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses, i, maddr))
1380 return FALSE;
1381 }
1382
1383 return TRUE;
1384}
1385
1386static BOOL setup_string(wArrayList* list)
1387{
1388 WINPR_ASSERT(list);
1389
1390 wObject* obj = ArrayList_Object(list);
1391 if (!obj)
1392 return FALSE;
1393 obj->fnObjectFree = free;
1394 // obj->fnObjectNew = wwinpr_ObjectStringClone;
1395 return TRUE;
1396}
1397
1398rdpAssistanceFile* freerdp_assistance_file_new(void)
1399{
1400 winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
1401 rdpAssistanceFile* file = calloc(1, sizeof(rdpAssistanceFile));
1402 if (!file)
1403 return nullptr;
1404
1405 file->MachineAddresses = ArrayList_New(FALSE);
1406 file->MachinePorts = ArrayList_New(FALSE);
1407 file->MachineUris = ArrayList_New(FALSE);
1408
1409 if (!file->MachineAddresses || !file->MachinePorts || !file->MachineUris)
1410 goto fail;
1411
1412 if (!setup_string(file->MachineAddresses) || !setup_string(file->MachineUris))
1413 goto fail;
1414
1415 return file;
1416
1417fail:
1418 WINPR_PRAGMA_DIAG_PUSH
1419 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1420 freerdp_assistance_file_free(file);
1421 WINPR_PRAGMA_DIAG_POP
1422 return nullptr;
1423}
1424
1425void freerdp_assistance_file_free(rdpAssistanceFile* file)
1426{
1427 if (!file)
1428 return;
1429
1430 update_password(file, nullptr);
1431 update_connectionstring2(file, nullptr, 0);
1432 free(file->filename);
1433 free(file->Username);
1434 free(file->LHTicket);
1435 free(file->RCTicket);
1436 free(file->PassStub);
1437 free(file->ConnectionString1);
1438 free(file->EncryptedLHTicket);
1439 free(file->RASessionId);
1440 free(file->RASpecificParams);
1441 free(file->RASpecificParams2);
1442 free(file->EncryptedPassStub);
1443
1444 ArrayList_Free(file->MachineAddresses);
1445 ArrayList_Free(file->MachinePorts);
1446 ArrayList_Free(file->MachineUris);
1447 free(file);
1448}
1449
1450void freerdp_assistance_print_file(rdpAssistanceFile* file, wLog* log, DWORD level)
1451{
1452 WINPR_ASSERT(file);
1453
1454 WLog_Print(log, level, "Username: %s", file->Username);
1455 WLog_Print(log, level, "LHTicket: %s", file->LHTicket);
1456 WLog_Print(log, level, "RCTicket: %s", file->RCTicket);
1457 WLog_Print(log, level, "RCTicketEncrypted: %" PRId32, file->RCTicketEncrypted);
1458 WLog_Print(log, level, "PassStub: %s", file->PassStub);
1459 WLog_Print(log, level, "DtStart: %" PRIu32, file->DtStart);
1460 WLog_Print(log, level, "DtLength: %" PRIu32, file->DtLength);
1461 WLog_Print(log, level, "LowSpeed: %" PRId32, file->LowSpeed);
1462 WLog_Print(log, level, "RASessionId: %s", file->RASessionId);
1463 WLog_Print(log, level, "RASpecificParams: %s", file->RASpecificParams);
1464 WLog_Print(log, level, "RASpecificParams2: %s", file->RASpecificParams2);
1465
1466 for (size_t x = 0; x < ArrayList_Count(file->MachineAddresses); x++)
1467 {
1468 UINT32 port = 0;
1469 const char* uri = nullptr;
1470 const char* addr = ArrayList_GetItem(file->MachineAddresses, x);
1471 if (x < ArrayList_Count(file->MachinePorts))
1472 {
1473 union
1474 {
1475 uintptr_t port;
1476 void* data;
1477 } cnv;
1478 cnv.data = ArrayList_GetItem(file->MachinePorts, x);
1479 WINPR_ASSERT(cnv.port <= UINT32_MAX);
1480 port = (UINT32)cnv.port;
1481 }
1482 if (x < ArrayList_Count(file->MachineUris))
1483 uri = ArrayList_GetItem(file->MachineUris, x);
1484
1485 WLog_Print(log, level, "MachineAddress [%" PRIuz ": %s", x, addr);
1486 WLog_Print(log, level, "MachinePort [%" PRIuz ": %" PRIu32, x, port);
1487 WLog_Print(log, level, "MachineURI [%" PRIuz ": %s", x, uri);
1488 }
1489}
1490
1491BOOL freerdp_assistance_get_encrypted_pass_stub(rdpAssistanceFile* file, const char** pwd,
1492 size_t* size)
1493{
1494 if (!file || !pwd || !size)
1495 return FALSE;
1496
1497 *pwd = (const char*)file->EncryptedPassStub;
1498 *size = file->EncryptedPassStubLength;
1499 return TRUE;
1500}
1501
1502int freerdp_assistance_set_connection_string2(rdpAssistanceFile* file, const char* string,
1503 const char* password)
1504{
1505 if (!file || !string || !password)
1506 return -1;
1507
1508 char* str = _strdup(string);
1509 if (!str)
1510 return -1;
1511
1512 if (!update_connectionstring2_nocopy(file, str))
1513 return -1;
1514 if (!update_password(file, password))
1515 return -1;
1516 return freerdp_assistance_parse_connection_string2(file);
1517}
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.
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.
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:58