FreeRDP
Loading...
Searching...
No Matches
client/common/client.c
1
21#include <winpr/cast.h>
22#include <winpr/i18n.h>
23
24#include <freerdp/config.h>
25
26#include <string.h>
27#include <errno.h>
28#include <math.h>
29#include <limits.h>
30#include <float.h>
31
32#include <freerdp/client.h>
33
34#include <freerdp/freerdp.h>
35#include <freerdp/addin.h>
36#include <freerdp/assistance.h>
37#include <freerdp/client/file.h>
38#include <freerdp/utils/passphrase.h>
39#include <freerdp/client/cmdline.h>
40#include <freerdp/client/channels.h>
41#include <freerdp/event.h>
42#include <freerdp/utils/smartcardlogon.h>
43#include <freerdp/session.h>
44
45#if defined(CHANNEL_AINPUT_CLIENT)
46#include <freerdp/client/ainput.h>
47#include <freerdp/channels/ainput.h>
48#endif
49
50#if defined(CHANNEL_VIDEO_CLIENT)
51#include <freerdp/client/video.h>
52#include <freerdp/channels/video.h>
53#endif
54
55#if defined(CHANNEL_RDPGFX_CLIENT)
56#include <freerdp/client/rdpgfx.h>
57#include <freerdp/channels/rdpgfx.h>
58#include <freerdp/gdi/gfx.h>
59#endif
60
61#if defined(CHANNEL_GEOMETRY_CLIENT)
62#include <freerdp/client/geometry.h>
63#include <freerdp/channels/geometry.h>
64#endif
65
66#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT)
67#include <freerdp/gdi/video.h>
68#endif
69
70#include <freerdp/channels/rdpewa.h>
71
72#ifdef WITH_AAD
73#include <freerdp/utils/http.h>
74#include <freerdp/utils/aad.h>
75#include <freerdp/client/aad_helper.h>
76#endif
77
78#ifdef WITH_SSO_MIB
79#include "sso_mib_tokens.h"
80#endif
81
82#include "oauth2.h"
83
84#include <freerdp/log.h>
85
86BOOL freerdp_client_enable_translation(const char* locale, const char* searchPath)
87{
88 return freerdp_enable_translation(locale, searchPath) &&
89 winpr_i18n_bind_domain("freerdp-client", searchPath);
90}
91#define TAG CLIENT_TAG("common")
92
93typedef struct
94{
95 freerdp* instance;
96 window_events_fkt_t window_events;
97 bool running;
98} handle_window_events_t;
99
100WINPR_ATTR_NODISCARD
101static BOOL requireRdpClientContext(const rdpContext* context)
102{
103 if (!context)
104 {
105 WLog_ERR(TAG, "[APPLICATION BUG] Function must be called with a context != NULL!");
106 return FALSE;
107 }
108
109 freerdp* instance = context->instance;
110 if (!instance)
111 {
112 WLog_ERR(TAG,
113 "[APPLICATION BUG] Function must be called with a context->instance != NULL!");
114 return FALSE;
115 }
116
117 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
118 if (!pEntryPoints)
119 {
120 WLog_ERR(TAG, "[APPLICATION BUG] Function must be called with a "
121 "context->instance->pclientEntryPoints != NULL!");
122 return FALSE;
123 }
124
125 /* This function must be called with a context containing rdpClientContext. So if the size is
126 * not large enough that is a terminal failure. */
127 if (sizeof(rdpClientContext) > instance->ContextSize)
128 {
129 WLog_ERR(
130 TAG,
131 "[APPLICATION BUG] Function must be called with a context based on rdpClientContext!");
132 WLog_ERR(TAG,
133 "[APPLICATION BUG] sizeof(rdpClientContext)[%" PRIuz
134 "] > instance->ContextSize[%" PRIuz "])",
135 sizeof(rdpClientContext), instance->ContextSize);
136 return FALSE;
137 }
138
139 return TRUE;
140}
141
142static void set_default_callbacks(freerdp* instance)
143{
144 WINPR_ASSERT(instance);
145 instance->AuthenticateEx = client_cli_authenticate_ex;
146 instance->ChooseSmartcard = client_cli_choose_smartcard;
147 instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
148 instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
149 instance->PresentGatewayMessage = client_cli_present_gateway_message;
150 instance->LogonErrorInfo = client_cli_logon_error_info;
151 instance->GetAccessToken = client_cli_get_access_token;
152 instance->RetryDialog = client_common_retry_dialog;
153
154 WINPR_ASSERT(instance->context);
155 WINPR_ASSERT(instance->context->update);
156 instance->context->update->SaveSessionInfo = client_common_save_session_info;
157}
158
159static void client_cli_user_notification(void* context, const UserNotificationEventArgs* e)
160{
161 WINPR_UNUSED(context);
162 WINPR_ASSERT(e);
163 if (strcmp(e->e.Sender, RDPEWA_CHANNEL_NAME) != 0)
164 return;
165
166 if (!e->message || e->message[0] == '\0')
167 return;
168 (void)fprintf(stderr, "[%s] Touch the security key\n", e->e.Sender);
169 (void)fflush(stderr);
170}
171
172WINPR_ATTR_NODISCARD
173static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
174{
175 WINPR_ASSERT(instance);
176 WINPR_ASSERT(context);
177
178 instance->LoadChannels = freerdp_client_load_channels;
179 set_default_callbacks(instance);
180
181 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
182 WINPR_ASSERT(pEntryPoints);
183
184 if (requireRdpClientContext(context))
185 {
186 rdpClientContext* cctx = (rdpClientContext*)context;
187 cctx->oauth2 = freerdp_oauth2_new();
188 if (!cctx->oauth2)
189 return FALSE;
190 }
191
192 return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context);
193}
194
195static void freerdp_client_common_free(freerdp* instance, rdpContext* context)
196{
197 WINPR_ASSERT(instance);
198 WINPR_ASSERT(context);
199
200 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
201 WINPR_ASSERT(pEntryPoints);
202 IFCALL(pEntryPoints->ClientFree, instance, context);
203
204 if (requireRdpClientContext(context))
205 {
206 rdpClientContext* cctx = (rdpClientContext*)context;
207 freerdp_oauth2_free(cctx->oauth2);
208 cctx->oauth2 = nullptr;
209 }
210}
211
212/* Common API */
213
214rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
215{
216 freerdp* instance = nullptr;
217 rdpContext* context = nullptr;
218
219 if (!pEntryPoints)
220 return nullptr;
221
222 if (!IFCALLRESULT(TRUE, pEntryPoints->GlobalInit))
223 return nullptr;
224
225 instance = freerdp_new();
226
227 if (!instance)
228 return nullptr;
229
230 instance->ContextSize = pEntryPoints->ContextSize;
231 instance->ContextNew = freerdp_client_common_new;
232 instance->ContextFree = freerdp_client_common_free;
233 instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size);
234
235 if (!instance->pClientEntryPoints)
236 goto out_fail;
237
238 CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size);
239
240 if (!freerdp_context_new_ex(instance, pEntryPoints->settings))
241 goto out_fail2;
242
243 context = instance->context;
244 context->instance = instance;
245
246#if defined(WITH_CLIENT_CHANNELS)
247 if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
248 CHANNEL_RC_OK)
249 goto out_fail2;
250#endif
251
252 return context;
253out_fail2:
254 free(instance->pClientEntryPoints);
255out_fail:
256 freerdp_free(instance);
257 return nullptr;
258}
259
260void freerdp_client_context_free(rdpContext* context)
261{
262 freerdp* instance = nullptr;
263
264 if (!context)
265 return;
266
267 instance = context->instance;
268
269 if (instance)
270 {
271 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
272 freerdp_context_free(instance);
273
274 if (pEntryPoints)
275 IFCALL(pEntryPoints->GlobalUninit);
276
277 free(instance->pClientEntryPoints);
278 freerdp_free(instance);
279 }
280}
281
282int freerdp_client_start(rdpContext* context)
283{
284 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr;
285
286 if (!context || !context->instance || !context->instance->pClientEntryPoints)
287 return ERROR_BAD_ARGUMENTS;
288
289 if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
290 {
291 set_default_callbacks(context->instance);
292 if (context->pubSub)
293 {
294 const int rc =
295 PubSub_SubscribeUserNotification(context->pubSub, client_cli_user_notification);
296 if (rc < 0)
297 return FALSE;
298 }
299 }
300
301#ifdef WITH_SSO_MIB
302 if (requireRdpClientContext(context))
303 {
304 rdpClientContext* client_context = (rdpClientContext*)context;
305 client_context->mibClientWrapper = sso_mib_new(context);
306 if (!client_context->mibClientWrapper)
307 return ERROR_INTERNAL_ERROR;
308 }
309#endif
310
311 pEntryPoints = context->instance->pClientEntryPoints;
312 return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context);
313}
314
315int freerdp_client_stop(rdpContext* context)
316{
317 RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr;
318
319 if (!context || !context->instance || !context->instance->pClientEntryPoints)
320 return ERROR_BAD_ARGUMENTS;
321
322 pEntryPoints = context->instance->pClientEntryPoints;
323 const int rc = IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context);
324
325 if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
326 PubSub_UnsubscribeUserNotification(context->pubSub, client_cli_user_notification);
327
328#ifdef WITH_SSO_MIB
329 if (requireRdpClientContext(context))
330 {
331 rdpClientContext* client_context = (rdpClientContext*)context;
332 sso_mib_free(client_context->mibClientWrapper);
333 client_context->mibClientWrapper = nullptr;
334 }
335#endif // WITH_SSO_MIB
336 return rc;
337}
338
339freerdp* freerdp_client_get_instance(rdpContext* context)
340{
341 if (!context || !context->instance)
342 return nullptr;
343
344 return context->instance;
345}
346
347HANDLE freerdp_client_get_thread(rdpContext* context)
348{
349 if (!context)
350 return nullptr;
351
352 WINPR_ASSERT(requireRdpClientContext(context));
353 if (!requireRdpClientContext(context))
354 return nullptr;
355
356 return ((rdpClientContext*)context)->thread;
357}
358
359WINPR_ATTR_NODISCARD
360static BOOL freerdp_client_settings_post_process(rdpSettings* settings)
361{
362 /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so
363 * that the rdp file also triggers this functionality */
364 if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled))
365 {
366 if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
367 {
368 const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
369 const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
370 if (Username)
371 {
372 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username))
373 goto out_error;
374 }
375
376 if (Domain)
377 {
378 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain))
379 goto out_error;
380 }
381
382 if (freerdp_settings_get_string(settings, FreeRDP_Password))
383 {
385 settings, FreeRDP_GatewayPassword,
386 freerdp_settings_get_string(settings, FreeRDP_Password)))
387 goto out_error;
388 }
389 }
390 }
391
392 /* Moved logic for Multimon and Span monitors to force fullscreen, so
393 * that the rdp file also triggers this functionality */
394 if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors))
395 {
396 if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
397 goto out_error;
398 if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE))
399 goto out_error;
400 }
401 else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
402 {
403 if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE))
404 goto out_error;
405 }
406
407 /* deal with the smartcard / smartcard logon stuff */
408 if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
409 {
410 if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE))
411 goto out_error;
412 if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
413 goto out_error;
414 if (!freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE))
415 goto out_error;
416 }
417
418 return TRUE;
419out_error:
420 return FALSE;
421}
422
423int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv,
424 BOOL allowUnknown)
425
426{
427 return freerdp_client_settings_parse_command_line_ex(settings, argc, argv, allowUnknown,
428 nullptr, 0, nullptr, nullptr);
429}
430
431int freerdp_client_settings_parse_command_line_ex(
432 rdpSettings* settings, int argc, char** argv, BOOL allowUnknown, COMMAND_LINE_ARGUMENT_A* args,
433 size_t count, freerdp_command_line_handle_option_t handle_option, void* handle_userdata)
434{
435 int status = 0;
436
437 if (argc < 1)
438 return 0;
439
440 if (!argv)
441 return -1;
442
443 status = freerdp_client_settings_parse_command_line_arguments_ex(
444 settings, argc, argv, allowUnknown, args, count, handle_option, handle_userdata);
445
446 if (status < 0)
447 return status;
448
449 /* This function will call logic that is applicable to the settings
450 * from command line parsing AND the rdp file parsing */
451 if (!freerdp_client_settings_post_process(settings))
452 status = -1;
453
454 const char* name = argv[0];
455 WLog_DBG(TAG, "This is [%s] %s %s", name, freerdp_get_version_string(),
456 freerdp_get_build_config());
457 return status;
458}
459
460int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename)
461{
462 rdpFile* file = nullptr;
463 int ret = -1;
464 file = freerdp_client_rdp_file_new();
465
466 if (!file)
467 return -1;
468
469 if (!freerdp_client_parse_rdp_file(file, filename))
470 goto out;
471
472 if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
473 goto out;
474
475 ret = 0;
476out:
477 freerdp_client_rdp_file_free(file);
478 return ret;
479}
480
481int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer,
482 size_t size)
483{
484 rdpFile* file = nullptr;
485 int status = -1;
486 file = freerdp_client_rdp_file_new();
487
488 if (!file)
489 return -1;
490
491 if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) &&
492 freerdp_client_populate_settings_from_rdp_file(file, settings))
493 {
494 status = 0;
495 }
496
497 freerdp_client_rdp_file_free(file);
498 return status;
499}
500
501int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename,
502 BOOL unicode)
503{
504 rdpFile* file = nullptr;
505 int ret = -1;
506 file = freerdp_client_rdp_file_new();
507
508 if (!file)
509 return -1;
510
511 if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
512 goto out;
513
514 if (!freerdp_client_write_rdp_file(file, filename, unicode))
515 goto out;
516
517 ret = 0;
518out:
519 freerdp_client_rdp_file_free(file);
520 return ret;
521}
522
523int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[])
524{
525 int status = 0;
526 int ret = -1;
527 char* filename = nullptr;
528 char* password = nullptr;
529 rdpAssistanceFile* file = nullptr;
530
531 if (!settings || !argv || (argc < 2))
532 return -1;
533
534 filename = argv[1];
535
536 for (int x = 2; x < argc; x++)
537 {
538 const char* key = strstr(argv[x], "assistance:");
539
540 if (key)
541 {
542 char* sep = strchr(key, ':');
543 if (!sep)
544 return -1;
545
546 password = sep + 1;
547 }
548 }
549
550 file = freerdp_assistance_file_new();
551
552 if (!file)
553 return -1;
554
555 status = freerdp_assistance_parse_file(file, filename, password);
556
557 if (status < 0)
558 goto out;
559
560 if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings))
561 goto out;
562
563 ret = 0;
564out:
565 freerdp_assistance_file_free(file);
566 return ret;
567}
568
569WINPR_ATTR_NODISCARD
570static int client_cli_read_string(freerdp* instance, const char* what, const char* suggestion,
571 char** result)
572{
573 WINPR_ASSERT(instance);
574 WINPR_ASSERT(what);
575 WINPR_ASSERT(result);
576
577 size_t size = 0;
578 printf("%s", what);
579 (void)fflush(stdout);
580
581 char* line = nullptr;
582 if (suggestion && strlen(suggestion) > 0)
583 {
584 line = _strdup(suggestion);
585 size = strlen(suggestion);
586 }
587
588 const SSIZE_T rc = freerdp_interruptible_get_line(instance->context, &line, &size, stdin);
589 if (rc < 0)
590 {
591 char ebuffer[256] = WINPR_C_ARRAY_INIT;
592 WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
593 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
594 free(line);
595 return -1;
596 }
597
598 free(*result);
599 *result = nullptr;
600
601 if (line)
602 {
603 line = StrSep(&line, "\r");
604 line = StrSep(&line, "\n");
605 *result = line;
606 }
607 return 0;
608}
609
625WINPR_ATTR_NODISCARD
626static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username,
627 char** password, char** domain)
628{
629 static const size_t password_size = 512;
630 const char* userAuth = "Username: ";
631 const char* domainAuth = "Domain: ";
632 const char* pwdAuth = "Password: ";
633 BOOL pinOnly = FALSE;
634 BOOL queryAll = FALSE;
635
636 WINPR_ASSERT(instance);
637 WINPR_ASSERT(instance->context);
638 WINPR_ASSERT(instance->context->settings);
639
640 switch (reason)
641 {
642 case AUTH_SMARTCARD_PIN:
643 pwdAuth = "Smartcard-Pin: ";
644 pinOnly = TRUE;
645 break;
646 case AUTH_FIDO_PIN:
647 pwdAuth = "FIDO2 PIN: ";
648 pinOnly = TRUE;
649 break;
650 case AUTH_RDSTLS:
651 queryAll = TRUE;
652 break;
653 case AUTH_TLS:
654 case AUTH_RDP:
655 case AUTH_NLA:
656 break;
657 case GW_AUTH_HTTP:
658 case GW_AUTH_RDG:
659 case GW_AUTH_RPC:
660 userAuth = "GatewayUsername: ";
661 domainAuth = "GatewayDomain: ";
662 pwdAuth = "GatewayPassword: ";
663 break;
664 default:
665 return FALSE;
666 }
667
668 if (!username || !password || !domain)
669 return FALSE;
670
671 if (!pinOnly)
672 {
673 const char* suggest = *username;
674 if (queryAll || !suggest)
675 {
676 const int rc = client_cli_read_string(instance, userAuth, suggest, username);
677 if (rc < 0)
678 goto fail;
679 }
680 }
681
682 if (!pinOnly)
683 {
684 const char* suggest = *domain;
685 if (queryAll || !suggest)
686 {
687 const int rc = client_cli_read_string(instance, domainAuth, suggest, domain);
688 if (rc < 0)
689 goto fail;
690 }
691 }
692
693 {
694 char* line = calloc(password_size, sizeof(char));
695
696 if (!line)
697 goto fail;
698
699 const BOOL fromStdin =
700 freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
701 const char* rc =
702 freerdp_passphrase_read(instance->context, pwdAuth, line, password_size, fromStdin);
703 if (rc == nullptr)
704 goto fail;
705
706 if (password_size > 0)
707 {
708 free(*password);
709 *password = line;
710 }
711 }
712
713 return TRUE;
714fail:
715 free(*username);
716 free(*domain);
717 free(*password);
718 *username = nullptr;
719 *domain = nullptr;
720 *password = nullptr;
721 return FALSE;
722}
723
724BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
725 rdp_auth_reason reason)
726{
727 WINPR_ASSERT(instance);
728 WINPR_ASSERT(username);
729 WINPR_ASSERT(password);
730 WINPR_ASSERT(domain);
731
732 switch (reason)
733 {
734 case AUTH_RDSTLS:
735 case AUTH_NLA:
736 break;
737
738 case AUTH_TLS:
739 case AUTH_RDP:
740 case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
741 case AUTH_FIDO_PIN:
742 if ((*username) && (*password))
743 return TRUE;
744 break;
745 case GW_AUTH_HTTP:
746 case GW_AUTH_RDG:
747 case GW_AUTH_RPC:
748 break;
749 default:
750 return FALSE;
751 }
752
753 return client_cli_authenticate_raw(instance, reason, username, password, domain);
754}
755
756BOOL client_cli_choose_smartcard(WINPR_ATTR_UNUSED freerdp* instance, SmartcardCertInfo** cert_list,
757 DWORD count, DWORD* choice, BOOL gateway)
758{
759 unsigned long answer = 0;
760 char* p = nullptr;
761
762 printf("Multiple smartcards are available for use:\n");
763 for (DWORD i = 0; i < count; i++)
764 {
765 const SmartcardCertInfo* cert = cert_list[i];
766 char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
767 char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
768
769 printf("[%" PRIu32
770 "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n",
771 i, container_name, reader, cert->userHint, cert->domainHint, cert->subject,
772 cert->issuer, cert->upn);
773
774 free(reader);
775 free(container_name);
776 }
777
778 while (1)
779 {
780 char input[10] = WINPR_C_ARRAY_INIT;
781
782 printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ",
783 gateway ? "gateway authentication" : "logon", count - 1);
784 (void)fflush(stdout);
785 if (!fgets(input, 10, stdin))
786 {
787 WLog_ERR(TAG, "could not read from stdin");
788 return FALSE;
789 }
790
791 answer = strtoul(input, &p, 10);
792 if ((*p == '\n' && p != input) && answer < count)
793 {
794 *choice = (UINT32)answer;
795 return TRUE;
796 }
797 }
798}
799
800#if defined(WITH_FREERDP_DEPRECATED)
801BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain)
802{
803 if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon))
804 {
805 WLog_INFO(TAG, "Authentication via smartcard");
806 return TRUE;
807 }
808
809 return client_cli_authenticate_raw(instance, FALSE, username, password, domain);
810}
811
812BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain)
813{
814 return client_cli_authenticate_raw(instance, TRUE, username, password, domain);
815}
816#endif
817
818WINPR_ATTR_NODISCARD
819static DWORD client_cli_accept_certificate(freerdp* instance)
820{
821 int answer = 0;
822
823 WINPR_ASSERT(instance);
824 WINPR_ASSERT(instance->context);
825
826 const rdpSettings* settings = instance->context->settings;
827 WINPR_ASSERT(settings);
828
829 const BOOL fromStdin = freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin);
830 if (fromStdin)
831 return 0;
832
833 while (1)
834 {
835 printf("Do you trust the above certificate? (Y/T/N) ");
836 (void)fflush(stdout);
837 answer = freerdp_interruptible_getc(instance->context, stdin);
838
839 if ((answer == EOF) || feof(stdin))
840 {
841 printf("\nError: Could not read answer from stdin.\n");
842 return 0;
843 }
844
845 switch (answer)
846 {
847 case 'y':
848 case 'Y':
849 answer = freerdp_interruptible_getc(instance->context, stdin);
850 printf("\n");
851 if (answer == EOF)
852 return 0;
853 return 1;
854
855 case 't':
856 case 'T':
857 answer = freerdp_interruptible_getc(instance->context, stdin);
858 printf("\n");
859 if (answer == EOF)
860 return 0;
861 return 2;
862
863 case 'n':
864 case 'N':
865 answer = freerdp_interruptible_getc(instance->context, stdin);
866 printf("\n");
867 if (answer == EOF)
868 return 0;
869 return 0;
870
871 default:
872 break;
873 }
874 }
875}
876
890#if defined(WITH_FREERDP_DEPRECATED)
891DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject,
892 const char* issuer, const char* fingerprint, BOOL host_mismatch)
893{
894 WINPR_UNUSED(common_name);
895 WINPR_UNUSED(host_mismatch);
896
897 printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n");
898 printf("Certificate details:\n");
899 printf("\tSubject: %s\n", subject);
900 printf("\tIssuer: %s\n", issuer);
901 printf("\tThumbprint: %s\n", fingerprint);
902 printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
903 "the CA certificate in your certificate store, or the certificate has expired.\n"
904 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
905 return client_cli_accept_certificate(instance);
906}
907#endif
908
909WINPR_ATTR_MALLOC(free, 1)
910static char* client_cli_pem_cert(const char* pem)
911{
912 rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
913 if (!cert)
914 return nullptr;
915
916 char* fp = freerdp_certificate_get_fingerprint(cert);
917 char* start = freerdp_certificate_get_validity(cert, TRUE);
918 char* end = freerdp_certificate_get_validity(cert, FALSE);
919 freerdp_certificate_free(cert);
920
921 char* str = nullptr;
922 size_t slen = 0;
923 winpr_asprintf(&str, &slen,
924 "\tValid from: %s\n"
925 "\tValid to: %s\n"
926 "\tThumbprint: %s\n",
927 start, end, fp);
928 free(fp);
929 free(start);
930 free(end);
931 return str;
932}
933
949DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
950 const char* common_name, const char* subject,
951 const char* issuer, const char* fingerprint, DWORD flags)
952{
953 const char* type = "RDP-Server";
954
955 WINPR_ASSERT(instance);
956 WINPR_ASSERT(instance->context);
957 WINPR_ASSERT(instance->context->settings);
958
959 if (flags & VERIFY_CERT_FLAG_GATEWAY)
960 type = "RDP-Gateway";
961
962 if (flags & VERIFY_CERT_FLAG_REDIRECT)
963 type = "RDP-Redirect";
964
965 printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type);
966 printf("\tCommon Name: %s\n", common_name);
967 printf("\tSubject: %s\n", subject);
968 printf("\tIssuer: %s\n", issuer);
969 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
970 * FreeRDP_CertificateCallbackPreferPEM to TRUE
971 */
972 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
973 {
974 char* str = client_cli_pem_cert(fingerprint);
975 printf("%s", str);
976 free(str);
977 }
978 else
979 printf("\tThumbprint: %s\n", fingerprint);
980
981 printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
982 "the CA certificate in your certificate store, or the certificate has expired.\n"
983 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
984 return client_cli_accept_certificate(instance);
985}
986
1002#if defined(WITH_FREERDP_DEPRECATED)
1003DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name,
1004 const char* subject, const char* issuer,
1005 const char* fingerprint, const char* old_subject,
1006 const char* old_issuer, const char* old_fingerprint)
1007{
1008 WINPR_UNUSED(common_name);
1009
1010 printf("WARNING: This callback is deprecated, migrate to "
1011 "client_cli_verify_changed_certificate_ex\n");
1012 printf("!!! Certificate has changed !!!\n");
1013 printf("\n");
1014 printf("New Certificate details:\n");
1015 printf("\tSubject: %s\n", subject);
1016 printf("\tIssuer: %s\n", issuer);
1017 printf("\tThumbprint: %s\n", fingerprint);
1018 printf("\n");
1019 printf("Old Certificate details:\n");
1020 printf("\tSubject: %s\n", old_subject);
1021 printf("\tIssuer: %s\n", old_issuer);
1022 printf("\tThumbprint: %s\n", old_fingerprint);
1023 printf("\n");
1024 printf("The above X.509 certificate does not match the certificate used for previous "
1025 "connections.\n"
1026 "This may indicate that the certificate has been tampered with.\n"
1027 "Please contact the administrator of the RDP server and clarify.\n");
1028 return client_cli_accept_certificate(instance);
1029}
1030#endif
1031
1051DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
1052 const char* common_name, const char* subject,
1053 const char* issuer, const char* fingerprint,
1054 const char* old_subject, const char* old_issuer,
1055 const char* old_fingerprint, DWORD flags)
1056{
1057 const char* type = "RDP-Server";
1058
1059 WINPR_ASSERT(instance);
1060 WINPR_ASSERT(instance->context);
1061 WINPR_ASSERT(instance->context->settings);
1062
1063 if (flags & VERIFY_CERT_FLAG_GATEWAY)
1064 type = "RDP-Gateway";
1065
1066 if (flags & VERIFY_CERT_FLAG_REDIRECT)
1067 type = "RDP-Redirect";
1068
1069 printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type);
1070 printf("\n");
1071 printf("New Certificate details:\n");
1072 printf("\tCommon Name: %s\n", common_name);
1073 printf("\tSubject: %s\n", subject);
1074 printf("\tIssuer: %s\n", issuer);
1075 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
1076 * FreeRDP_CertificateCallbackPreferPEM to TRUE
1077 */
1078 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
1079 {
1080 char* str = client_cli_pem_cert(fingerprint);
1081 printf("%s", str);
1082 free(str);
1083 }
1084 else
1085 printf("\tThumbprint: %s\n", fingerprint);
1086 printf("\n");
1087 printf("Old Certificate details:\n");
1088 printf("\tSubject: %s\n", old_subject);
1089 printf("\tIssuer: %s\n", old_issuer);
1090 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
1091 * FreeRDP_CertificateCallbackPreferPEM to TRUE
1092 */
1093 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
1094 {
1095 char* str = client_cli_pem_cert(old_fingerprint);
1096 printf("%s", str);
1097 free(str);
1098 }
1099 else
1100 printf("\tThumbprint: %s\n", old_fingerprint);
1101 printf("\n");
1102 if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
1103 {
1104 printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n");
1105 printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n");
1106 printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n");
1107 printf("\tAll manually accepted certificates must be reconfirmed!\n");
1108 printf("\n");
1109 }
1110 printf("The above X.509 certificate does not match the certificate used for previous "
1111 "connections.\n"
1112 "This may indicate that the certificate has been tampered with.\n"
1113 "Please contact the administrator of the RDP server and clarify.\n");
1114 return client_cli_accept_certificate(instance);
1115}
1116
1117BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
1118 BOOL isConsentMandatory, size_t length,
1119 const WCHAR* message)
1120{
1121 const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message";
1122
1123 WINPR_ASSERT(instance);
1124 WINPR_ASSERT(instance->context);
1125 WINPR_ASSERT(instance->context->settings);
1126
1127 if (!isDisplayMandatory && !isConsentMandatory)
1128 return TRUE;
1129
1130 printf("%s:\n", msgType);
1131 {
1132 LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), nullptr);
1133 if (!msg)
1134 {
1135 printf("Failed to convert message!\n");
1136 return FALSE;
1137 }
1138 printf("%s\n", msg);
1139 free(msg);
1140 }
1141
1142 while (isConsentMandatory)
1143 {
1144 printf("I understand and agree to the terms of this policy (Y/N) \n");
1145 (void)fflush(stdout);
1146 const int answer = freerdp_interruptible_getc(instance->context, stdin);
1147
1148 if ((answer == EOF) || feof(stdin))
1149 {
1150 printf("\nError: Could not read answer from stdin.\n");
1151 return FALSE;
1152 }
1153
1154 const int confirm = freerdp_interruptible_getc(instance->context, stdin);
1155 switch (answer)
1156 {
1157 case 'y':
1158 case 'Y':
1159 printf("\n");
1160 return confirm != EOF;
1161
1162 case 'n':
1163 case 'N':
1164 printf("\n");
1165 return FALSE;
1166
1167 default:
1168 break;
1169 }
1170 }
1171
1172 return TRUE;
1173}
1174
1175#if defined(WITH_AAD)
1176WINPR_ATTR_NODISCARD
1177static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
1178 const char* req_cnf, char** token)
1179{
1180 WINPR_ASSERT(instance);
1181 WINPR_ASSERT(instance->context);
1182
1183 size_t size = 0;
1184 char* url = nullptr;
1185 char* token_request = nullptr;
1186
1187 WINPR_ASSERT(scope);
1188 WINPR_ASSERT(req_cnf);
1189 WINPR_ASSERT(token);
1190
1191 BOOL rc = FALSE;
1192 *token = nullptr;
1193
1194 WINPR_ASSERT(requireRdpClientContext(instance->context));
1195 if (!requireRdpClientContext(instance->context))
1196 return FALSE;
1197
1198 char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1199 FREERDP_CLIENT_AAD_AUTH_REQUEST, scope);
1200
1201 printf("Browse to: %s\n", request);
1202 winpr_zfree(request);
1203 printf("Paste redirect URL here: \n");
1204
1205 if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
1206 goto cleanup;
1207
1208 {
1209 char* code =
1210 freerdp_client_extract_aad_code((rdpClientContext*)instance->context, url, size);
1211 if (!code)
1212 goto cleanup;
1213 token_request =
1214 freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1215 FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code, req_cnf);
1216 winpr_zfree(code);
1217 }
1218 if (!token_request)
1219 goto cleanup;
1220
1221 rc = client_common_get_access_token(instance, token_request, token);
1222
1223cleanup:
1224 winpr_zfree(token_request);
1225 free(url);
1226 return rc && (*token != nullptr);
1227}
1228
1229WINPR_ATTR_NODISCARD
1230static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
1231{
1232 WINPR_ASSERT(instance);
1233 WINPR_ASSERT(instance->context);
1234
1235 WINPR_ASSERT(requireRdpClientContext(instance->context));
1236 if (!requireRdpClientContext(instance->context))
1237 return FALSE;
1238
1239 size_t size = 0;
1240 char* url = nullptr;
1241 char* token_request = nullptr;
1242
1243 WINPR_ASSERT(token);
1244
1245 BOOL rc = FALSE;
1246
1247 *token = nullptr;
1248
1249 char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1250 FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST);
1251 if (!request)
1252 return FALSE;
1253 printf("Browse to: %s\n", request);
1254 winpr_zfree(request);
1255 printf("Paste redirect URL here: \n");
1256
1257 if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
1258 goto cleanup;
1259
1260 {
1261 char* code =
1262 freerdp_client_extract_aad_code((rdpClientContext*)instance->context, url, size);
1263 if (!code)
1264 goto cleanup;
1265 token_request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1266 FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code);
1267 winpr_zfree(code);
1268 }
1269
1270 if (!token_request)
1271 goto cleanup;
1272
1273 rc = client_common_get_access_token(instance, token_request, token);
1274
1275cleanup:
1276 free(token_request);
1277 free(url);
1278 return rc && (*token != nullptr);
1279}
1280#endif
1281
1282WINPR_ATTR_NODISCARD
1283static BOOL client_cli_get_access_token_va(freerdp* instance,
1284 WINPR_ATTR_UNUSED AccessTokenType tokenType,
1285 char** token, WINPR_ATTR_UNUSED size_t count, va_list ap)
1286{
1287 WINPR_ASSERT(instance);
1288 WINPR_ASSERT(token);
1289
1290 *token = nullptr;
1291#if !defined(WITH_AAD)
1292 WLog_ERR(TAG, "Build does not support AAD authentication");
1293 return FALSE;
1294#else
1295 BOOL rc = FALSE;
1296 WINPR_ASSERT(instance->context);
1297 const BOOL saved =
1298 freerdp_settings_get_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks);
1299 if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks,
1300 TRUE))
1301 return FALSE;
1302
1303 switch (tokenType)
1304 {
1305 case ACCESS_TOKEN_TYPE_AAD:
1306 {
1307 if (count < 2)
1308 {
1309 WLog_ERR(TAG,
1310 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
1311 ", aborting",
1312 count);
1313 }
1314 else
1315 {
1316 if (count > 2)
1317 WLog_WARN(
1318 TAG,
1319 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
1320 ", ignoring",
1321 count);
1322 const char* scope = va_arg(ap, const char*);
1323 const char* req_cnf = va_arg(ap, const char*);
1324 rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token);
1325 }
1326 }
1327 break;
1328 case ACCESS_TOKEN_TYPE_AVD:
1329 if (count != 0)
1330 WLog_WARN(TAG,
1331 "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
1332 ", ignoring",
1333 count);
1334 rc = client_cli_get_avd_access_token(instance, token);
1335 break;
1336 default:
1337 WLog_ERR(TAG, "Unexpected value for AccessTokenType [%u], aborting", tokenType);
1338 break;
1339 }
1340
1341 if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks,
1342 saved))
1343 return FALSE;
1344 return rc;
1345#endif
1346}
1347
1348BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
1349 size_t count, ...)
1350{
1351 va_list ap = WINPR_C_ARRAY_INIT;
1352 va_start(ap, count);
1353 const BOOL rc = client_cli_get_access_token_va(instance, tokenType, token, count, ap);
1354 va_end(ap);
1355 return rc;
1356}
1357
1358BOOL client_common_get_access_token(WINPR_ATTR_UNUSED freerdp* instance,
1359 WINPR_ATTR_UNUSED const char* request,
1360 WINPR_ATTR_UNUSED char** token)
1361{
1362 WINPR_ASSERT(request);
1363 WINPR_ASSERT(token);
1364
1365 *token = nullptr;
1366#ifdef WITH_AAD
1367 BOOL ret = FALSE;
1368 long resp_code = 0;
1369 BYTE* response = nullptr;
1370 size_t response_length = 0;
1371
1372 wLog* log = WLog_Get(TAG);
1373
1374 const char* token_ep =
1375 freerdp_utils_aad_get_wellknown_string(instance->context, AAD_WELLKNOWN_token_endpoint);
1376 if (!freerdp_http_request(token_ep, request, &resp_code, &response, &response_length))
1377 {
1378 WLog_ERR(TAG, "access token request failed");
1379 return FALSE;
1380 }
1381
1382 if (resp_code != HTTP_STATUS_OK)
1383 {
1384 char buffer[64] = WINPR_C_ARRAY_INIT;
1385
1386 WLog_Print(log, WLOG_ERROR,
1387 "Server unwilling to provide access token; returned status code %s",
1388 freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer)));
1389 if (response_length > 0)
1390 WLog_Print(log, WLOG_ERROR, "[status message] %s", response);
1391 goto cleanup;
1392 }
1393
1394 *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length);
1395 if (*token)
1396 ret = TRUE;
1397
1398cleanup:
1399 free(response);
1400 return ret;
1401#else
1402 WLog_ERR(TAG, "Build does not support AAD authentication");
1403 return FALSE;
1404#endif
1405}
1406
1407SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current,
1408 void* userarg)
1409{
1410 WINPR_UNUSED(instance);
1411 WINPR_ASSERT(instance->context);
1412 WINPR_UNUSED(userarg);
1413 WINPR_ASSERT(instance);
1414 WINPR_ASSERT(what);
1415
1416 if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
1417 {
1418 WLog_ERR(TAG, "Unknown module %s, aborting", what);
1419 return -1;
1420 }
1421
1422 if (current == 0)
1423 {
1424 if (strcmp(what, "arm-transport") == 0)
1425 WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what);
1426 }
1427
1428 const rdpSettings* settings = instance->context->settings;
1429 const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
1430 if (!enabled)
1431 {
1432 WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later");
1433 return -1;
1434 }
1435
1436 const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
1437 const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
1438 if (current >= max)
1439 {
1440 WLog_ERR(TAG,
1441 "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
1442 "tech support for help if this keeps happening.",
1443 what);
1444 return -1;
1445 }
1446
1447 WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt",
1448 what, current + 1, max, delay);
1449 return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
1450}
1451
1452BOOL client_auto_reconnect(freerdp* instance)
1453{
1454 return client_auto_reconnect_ex(instance, nullptr);
1455}
1456
1457WINPR_ATTR_NODISCARD
1458static BOOL client_auto_reconnect_do(freerdp* instance, BOOL retry, UINT32 numRetries,
1459 UINT32 maxRetries)
1460{
1461 WINPR_ASSERT(instance);
1462
1463 /* Perform an auto-reconnect. */
1464 while (retry)
1465 {
1466 /* Quit retrying if max retries has been exceeded */
1467 if ((maxRetries > 0) && (numRetries >= maxRetries))
1468 {
1469 WLog_DBG(TAG, "AutoReconnect retries exceeded.");
1470 return FALSE;
1471 }
1472
1473 /* Attempt the next reconnect */
1474 WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries);
1475
1476 const SSIZE_T delay =
1477 IFCALLRESULT(5000, instance->RetryDialog, instance, "connection", numRetries, nullptr);
1478 if (delay < 0)
1479 return FALSE;
1480 numRetries++;
1481
1482 if (freerdp_reconnect(instance))
1483 return TRUE;
1484
1485 switch (freerdp_get_last_error(instance->context))
1486 {
1487 case FREERDP_ERROR_CONNECT_CANCELLED:
1488 WLog_WARN(TAG, "Autoreconnect aborted by user");
1489 return FALSE;
1490 default:
1491 break;
1492 }
1493
1494 for (SSIZE_T x = 0; x < delay / 10; x++)
1495 Sleep(10);
1496 }
1497
1498 WLog_ERR(TAG, "Maximum reconnect retries exceeded");
1499 return FALSE;
1500}
1501
1502WINPR_ATTR_NODISCARD
1503static DWORD WINAPI handle_window_events(void* parg)
1504{
1505 handle_window_events_t* arg = parg;
1506 WINPR_ASSERT(arg);
1507
1508 while (arg->running)
1509 {
1510 WINPR_ASSERT(arg->window_events);
1511 if (!arg->window_events(arg->instance))
1512 {
1513 WLog_ERR(TAG, "window_events failed!");
1514 }
1515 Sleep(10);
1516 }
1517 return 0;
1518}
1519
1520BOOL client_auto_reconnect_ex(freerdp* instance, window_events_fkt_t window_events)
1521{
1522 BOOL retry = TRUE;
1523 UINT32 error = 0;
1524 UINT32 numRetries = 0;
1525 rdpSettings* settings = nullptr;
1526
1527 if (!instance)
1528 return FALSE;
1529
1530 WINPR_ASSERT(instance->context);
1531
1532 settings = instance->context->settings;
1533 WINPR_ASSERT(settings);
1534
1535 const UINT32 maxRetries =
1536 freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
1537
1538 /* Only auto reconnect on network disconnects. */
1539 error = freerdp_error_info(instance);
1540 switch (error)
1541 {
1542 case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
1543 /* A network disconnect was detected */
1544 WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]",
1545 freerdp_get_error_info_string(error));
1546 break;
1547 case ERRINFO_SUCCESS:
1548 /* A network disconnect was detected */
1549 WLog_INFO(TAG, "Network disconnect!");
1550 break;
1551 default:
1552 WLog_DBG(TAG, "Other error: %s", freerdp_get_error_info_string(error));
1553 return FALSE;
1554 }
1555
1556 if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
1557 {
1558 /* No auto-reconnect - just quit */
1559 WLog_DBG(TAG, "AutoReconnect not enabled, quitting.");
1560 return FALSE;
1561 }
1562
1563 const UINT err = freerdp_get_last_error(instance->context);
1564 switch (err)
1565 {
1566 case FREERDP_ERROR_CONNECT_LOGON_FAILURE:
1567 case FREERDP_ERROR_CONNECT_CLIENT_REVOKED:
1568 case FREERDP_ERROR_CONNECT_WRONG_PASSWORD:
1569 case FREERDP_ERROR_CONNECT_ACCESS_DENIED:
1570 case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION:
1571 case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT:
1572 case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED:
1573 case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS:
1574 WLog_WARN(TAG, "Connection aborted: credentials do not work [%s]",
1575 freerdp_get_last_error_name(err));
1576 return FALSE;
1577 case FREERDP_ERROR_CONNECT_CANCELLED:
1578 WLog_WARN(TAG, "Connection aborted by user");
1579 return FALSE;
1580 default:
1581 break;
1582 }
1583
1584 handle_window_events_t arg = { .instance = instance,
1585 .window_events = window_events,
1586 .running = true };
1587 HANDLE thread = nullptr;
1588 if (window_events)
1589 thread = CreateThread(nullptr, 0, handle_window_events, &arg, 0, nullptr);
1590 const BOOL rc = client_auto_reconnect_do(instance, retry, numRetries, maxRetries);
1591 if (thread)
1592 {
1593 arg.running = FALSE;
1594 WaitForSingleObject(thread, INFINITE);
1595 CloseHandle(thread);
1596 }
1597 return rc;
1598}
1599
1600int freerdp_client_common_stop(rdpContext* context)
1601{
1602 freerdp_abort_connect_context(context);
1603
1604 if (requireRdpClientContext(context))
1605 {
1606
1607 rdpClientContext* cctx = (rdpClientContext*)context;
1608 WINPR_ASSERT(cctx);
1609 if (cctx->thread)
1610 {
1611 (void)WaitForSingleObject(cctx->thread, INFINITE);
1612 (void)CloseHandle(cctx->thread);
1613 cctx->thread = nullptr;
1614 }
1615#if defined(WITH_AAD)
1616 aad_auth_helper_stop(cctx->aadHelper);
1617 cctx->aadHelper = nullptr;
1618#endif
1619 }
1620
1621 return 0;
1622}
1623
1624#if defined(CHANNEL_ENCOMSP_CLIENT)
1625BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp)
1626{
1627 BOOL state = 0;
1628
1629 if (!encomsp)
1630 return FALSE;
1631
1632 rdpClientContext* cctx = (rdpClientContext*)encomsp->custom;
1633 WINPR_ASSERT(cctx);
1634 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1635 if (!cctx || !requireRdpClientContext(&cctx->context))
1636 return FALSE;
1637
1638 state = cctx->controlToggle;
1639 cctx->controlToggle = !cctx->controlToggle;
1640 return freerdp_client_encomsp_set_control(encomsp, state);
1641}
1642
1643BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control)
1644{
1645 ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = WINPR_C_ARRAY_INIT;
1646
1647 if (!encomsp)
1648 return FALSE;
1649
1650 pdu.ParticipantId = encomsp->participantId;
1651 pdu.Flags = ENCOMSP_REQUEST_VIEW;
1652
1653 if (control)
1654 pdu.Flags |= ENCOMSP_REQUEST_INTERACT;
1655
1656 const UINT rc = encomsp->ChangeParticipantControlLevel(encomsp, &pdu);
1657 return rc == CHANNEL_RC_OK;
1658}
1659
1660static UINT
1661client_encomsp_participant_created(EncomspClientContext* context,
1662 const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated)
1663{
1664 BOOL request = 0;
1665
1666 if (!context || !context->custom || !participantCreated)
1667 return ERROR_INVALID_PARAMETER;
1668
1669 rdpClientContext* cctx = (rdpClientContext*)context->custom;
1670 WINPR_ASSERT(cctx);
1671 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1672 if (!cctx || !requireRdpClientContext(&cctx->context))
1673 return ERROR_INVALID_PARAMETER;
1674
1675 rdpSettings* settings = cctx->context.settings;
1676 WINPR_ASSERT(settings);
1677
1678 if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT)
1679 context->participantId = participantCreated->ParticipantId;
1680
1681 request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl);
1682 if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) &&
1683 !(participantCreated->Flags & ENCOMSP_MAY_INTERACT))
1684 {
1685 if (!freerdp_client_encomsp_set_control(context, TRUE))
1686 return ERROR_INTERNAL_ERROR;
1687
1688 /* if auto-request-control setting is enabled then only request control once upon connect,
1689 * otherwise it will auto request control again every time server turns off control which
1690 * is a bit annoying */
1691 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE))
1692 return ERROR_INTERNAL_ERROR;
1693 }
1694
1695 return CHANNEL_RC_OK;
1696}
1697
1698static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp)
1699{
1700 WINPR_ASSERT(cctx);
1701 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1702 if (!cctx || !requireRdpClientContext(&cctx->context))
1703 return;
1704
1705 cctx->encomsp = encomsp;
1706 encomsp->custom = (void*)cctx;
1707 encomsp->ParticipantCreated = client_encomsp_participant_created;
1708}
1709
1710static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp)
1711{
1712 if (encomsp)
1713 {
1714 encomsp->custom = nullptr;
1715 encomsp->ParticipantCreated = nullptr;
1716 }
1717
1718 if (cctx)
1719 {
1720 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1721 if (requireRdpClientContext(&cctx->context))
1722 cctx->encomsp = nullptr;
1723 }
1724}
1725#endif
1726
1727void freerdp_client_OnChannelConnectedEventHandler(void* context,
1728 const ChannelConnectedEventArgs* e)
1729{
1730 rdpClientContext* cctx = (rdpClientContext*)context;
1731
1732 WINPR_ASSERT(cctx);
1733 WINPR_ASSERT(e);
1734
1735 if (0)
1736 {
1737 }
1738#if defined(CHANNEL_AINPUT_CLIENT)
1739 else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
1740 {
1741
1742 WINPR_ASSERT(requireRdpClientContext(context));
1743 if (!requireRdpClientContext(context))
1744 return;
1745 cctx->ainput = (AInputClientContext*)e->pInterface;
1746 }
1747#endif
1748#if defined(CHANNEL_RDPEI_CLIENT)
1749 else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
1750 {
1751 WINPR_ASSERT(requireRdpClientContext(context));
1752 if (!requireRdpClientContext(context))
1753 return;
1754 cctx->rdpei = (RdpeiClientContext*)e->pInterface;
1755 }
1756#endif
1757#if defined(CHANNEL_RDPGFX_CLIENT)
1758 else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
1759 {
1760 gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
1761 }
1762#endif
1763#if defined(CHANNEL_GEOMETRY_CLIENT)
1764 else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
1765 {
1766 gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
1767 }
1768#endif
1769#if defined(CHANNEL_VIDEO_CLIENT)
1770 else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
1771 {
1772 gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1773 }
1774 else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
1775 {
1776 gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1777 }
1778#endif
1779#if defined(CHANNEL_ENCOMSP_CLIENT)
1780 else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
1781 {
1782 client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface);
1783 }
1784#endif
1785}
1786
1787void freerdp_client_OnChannelDisconnectedEventHandler(void* context,
1788 const ChannelDisconnectedEventArgs* e)
1789{
1790 rdpClientContext* cctx = (rdpClientContext*)context;
1791
1792 WINPR_ASSERT(cctx);
1793 WINPR_ASSERT(e);
1794
1795 if (0)
1796 {
1797 }
1798#if defined(CHANNEL_AINPUT_CLIENT)
1799 else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
1800 {
1801 WINPR_ASSERT(requireRdpClientContext(context));
1802 if (!requireRdpClientContext(context))
1803 return;
1804 cctx->ainput = nullptr;
1805 }
1806#endif
1807#if defined(CHANNEL_RDPEI_CLIENT)
1808 else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
1809 {
1810 WINPR_ASSERT(requireRdpClientContext(context));
1811 if (!requireRdpClientContext(context))
1812 return;
1813 cctx->rdpei = nullptr;
1814 }
1815#endif
1816#if defined(CHANNEL_RDPGFX_CLIENT)
1817 else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
1818 {
1819 gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
1820 }
1821#endif
1822#if defined(CHANNEL_GEOMETRY_CLIENT)
1823 else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
1824 {
1825 gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
1826 }
1827#endif
1828#if defined(CHANNEL_VIDEO_CLIENT)
1829 else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
1830 {
1831 gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1832 }
1833 else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
1834 {
1835 gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1836 }
1837#endif
1838#if defined(CHANNEL_ENCOMSP_CLIENT)
1839 else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
1840 {
1841 client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface);
1842 }
1843#endif
1844}
1845
1846BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags)
1847{
1848 BOOL handled = FALSE;
1849
1850 WINPR_ASSERT(cctx);
1851 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1852 if (!cctx || !requireRdpClientContext(&cctx->context))
1853 return FALSE;
1854
1855 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
1856 if (state != CONNECTION_STATE_ACTIVE)
1857 return TRUE;
1858
1859#if defined(CHANNEL_AINPUT_CLIENT)
1860 if (cctx->ainput)
1861 {
1862 UINT rc = 0;
1863 UINT64 flags = 0;
1864 INT32 x = 0;
1865 INT32 y = 0;
1866 INT32 value = mflags & 0xFF;
1867
1868 if (mflags & PTR_FLAGS_WHEEL_NEGATIVE)
1869 value = -1 * (0x100 - value);
1870
1871 /* We have discrete steps, scale this so we can also support high
1872 * resolution wheels. */
1873 value *= 0x10000;
1874
1875 if (mflags & PTR_FLAGS_WHEEL)
1876 {
1877 flags |= AINPUT_FLAGS_WHEEL;
1878 y = value;
1879 }
1880
1881 if (mflags & PTR_FLAGS_HWHEEL)
1882 {
1883 flags |= AINPUT_FLAGS_WHEEL;
1884 x = value;
1885 }
1886
1887 WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
1888 rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
1889 if (rc == CHANNEL_RC_OK)
1890 handled = TRUE;
1891 }
1892#endif
1893
1894 if (!handled)
1895 return freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0);
1896
1897 return TRUE;
1898}
1899
1900#if defined(CHANNEL_AINPUT_CLIENT)
1901WINPR_ATTR_NODISCARD
1902static inline BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y)
1903{
1904 WINPR_ASSERT(cctx);
1905 WINPR_ASSERT(cctx->ainput);
1906 WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
1907 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1908
1909 if (!cctx || !requireRdpClientContext(&cctx->context))
1910 return ERROR_INVALID_PARAMETER;
1911
1912 const UINT rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
1913
1914 return rc == CHANNEL_RC_OK;
1915}
1916#endif
1917
1918WINPR_ATTR_NODISCARD
1919static bool button_pressed(const rdpClientContext* cctx)
1920{
1921 WINPR_ASSERT(cctx);
1922 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1923 if (!cctx || !requireRdpClientContext(&cctx->context))
1924 return false;
1925
1926 for (size_t x = 0; x < ARRAYSIZE(cctx->pressed_buttons); x++)
1927 {
1928 const BOOL cur = cctx->pressed_buttons[x];
1929 if (cur)
1930 return true;
1931 }
1932 return false;
1933}
1934
1935BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x,
1936 INT32 y)
1937{
1938 BOOL handled = FALSE;
1939
1940 WINPR_ASSERT(cctx);
1941 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1942 if (!cctx || !requireRdpClientContext(&cctx->context))
1943 return FALSE;
1944
1945 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
1946 if (state != CONNECTION_STATE_ACTIVE)
1947 return TRUE;
1948
1949 if (mflags & PTR_FLAGS_BUTTON1)
1950 cctx->pressed_buttons[0] = mflags & PTR_FLAGS_DOWN;
1951 if (mflags & PTR_FLAGS_BUTTON2)
1952 cctx->pressed_buttons[1] = mflags & PTR_FLAGS_DOWN;
1953 if (mflags & PTR_FLAGS_BUTTON3)
1954 cctx->pressed_buttons[2] = mflags & PTR_FLAGS_DOWN;
1955
1956 if (((mflags & PTR_FLAGS_MOVE) != 0) &&
1957 !freerdp_settings_get_bool(cctx->context.settings, FreeRDP_MouseMotion))
1958 {
1959 if (!button_pressed(cctx))
1960 return TRUE;
1961 }
1962
1963 const BOOL haveRelative =
1964 freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent);
1965 if (relative && haveRelative)
1966 {
1967 return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags,
1968 WINPR_ASSERTING_INT_CAST(int16_t, x),
1969 WINPR_ASSERTING_INT_CAST(int16_t, y));
1970 }
1971
1972#if defined(CHANNEL_AINPUT_CLIENT)
1973 if (cctx->ainput)
1974 {
1975 UINT64 flags = 0;
1976
1977 if (cctx->mouse_grabbed && freerdp_client_use_relative_mouse_events(cctx))
1978 flags |= AINPUT_FLAGS_HAVE_REL;
1979
1980 if (relative)
1981 flags |= AINPUT_FLAGS_REL;
1982
1983 if (mflags & PTR_FLAGS_DOWN)
1984 flags |= AINPUT_FLAGS_DOWN;
1985 if (mflags & PTR_FLAGS_BUTTON1)
1986 flags |= AINPUT_FLAGS_BUTTON1;
1987 if (mflags & PTR_FLAGS_BUTTON2)
1988 flags |= AINPUT_FLAGS_BUTTON2;
1989 if (mflags & PTR_FLAGS_BUTTON3)
1990 flags |= AINPUT_FLAGS_BUTTON3;
1991 if (mflags & PTR_FLAGS_MOVE)
1992 flags |= AINPUT_FLAGS_MOVE;
1993 handled = ainput_send_diff_event(cctx, flags, x, y);
1994 }
1995#endif
1996
1997 if (!handled)
1998 {
1999 if (relative)
2000 {
2001 cctx->lastX += x;
2002 cctx->lastY += y;
2003 WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
2004 }
2005 else
2006 {
2007 cctx->lastX = x;
2008 cctx->lastY = y;
2009 }
2010 return freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
2011 (UINT16)cctx->lastY);
2012 }
2013 return TRUE;
2014}
2015
2016BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags,
2017 INT32 x, INT32 y)
2018{
2019 BOOL handled = FALSE;
2020 WINPR_ASSERT(cctx);
2021 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2022
2023 if (!cctx || !requireRdpClientContext(&cctx->context))
2024 return FALSE;
2025
2026 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2027 if (state != CONNECTION_STATE_ACTIVE)
2028 return TRUE;
2029
2030 if (mflags & PTR_XFLAGS_BUTTON1)
2031 cctx->pressed_buttons[3] = mflags & PTR_XFLAGS_DOWN;
2032 if (mflags & PTR_XFLAGS_BUTTON2)
2033 cctx->pressed_buttons[4] = mflags & PTR_XFLAGS_DOWN;
2034
2035 const BOOL haveRelative =
2036 freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent);
2037 if (relative && haveRelative)
2038 {
2039 return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags,
2040 WINPR_ASSERTING_INT_CAST(int16_t, x),
2041 WINPR_ASSERTING_INT_CAST(int16_t, y));
2042 }
2043
2044#if defined(CHANNEL_AINPUT_CLIENT)
2045 if (cctx->ainput)
2046 {
2047 UINT64 flags = 0;
2048
2049 if (relative)
2050 flags |= AINPUT_FLAGS_REL;
2051 if (mflags & PTR_XFLAGS_DOWN)
2052 flags |= AINPUT_FLAGS_DOWN;
2053 if (mflags & PTR_XFLAGS_BUTTON1)
2054 flags |= AINPUT_XFLAGS_BUTTON1;
2055 if (mflags & PTR_XFLAGS_BUTTON2)
2056 flags |= AINPUT_XFLAGS_BUTTON2;
2057
2058 handled = ainput_send_diff_event(cctx, flags, x, y);
2059 }
2060#endif
2061
2062 if (!handled)
2063 {
2064 if (relative)
2065 {
2066 cctx->lastX += x;
2067 cctx->lastY += y;
2068 WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
2069 }
2070 else
2071 {
2072 cctx->lastX = x;
2073 cctx->lastY = y;
2074 }
2075 freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
2076 (UINT16)cctx->lastY);
2077 }
2078
2079 return TRUE;
2080}
2081
2082WINPR_ATTR_NODISCARD
2083static BOOL freerdp_handle_touch_to_mouse(rdpClientContext* cctx, BOOL down,
2084 const FreeRDP_TouchContact* contact)
2085{
2086 WINPR_ASSERT(cctx);
2087 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2088 if (!cctx || !requireRdpClientContext(&cctx->context))
2089 return FALSE;
2090
2091 const UINT16 flags = PTR_FLAGS_MOVE | (down ? PTR_FLAGS_DOWN : 0);
2092 const UINT16 xflags = down ? PTR_XFLAGS_DOWN : 0;
2093 WINPR_ASSERT(contact);
2094 WINPR_ASSERT(contact->x <= UINT16_MAX);
2095 WINPR_ASSERT(contact->y <= UINT16_MAX);
2096
2097 switch (contact->count)
2098 {
2099 case 1:
2100 return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON1,
2101 contact->x, contact->y);
2102 case 2:
2103 return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON2,
2104 contact->x, contact->y);
2105 case 3:
2106 return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON3,
2107 contact->x, contact->y);
2108 case 4:
2109 return freerdp_client_send_extended_button_event(
2110 cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y);
2111 case 5:
2112 return freerdp_client_send_extended_button_event(
2113 cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y);
2114 default:
2115 /* unmapped events, ignore */
2116 return TRUE;
2117 }
2118}
2119
2120WINPR_ATTR_NODISCARD
2121static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2122{
2123 WINPR_ASSERT(cctx);
2124 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2125 WINPR_ASSERT(contact);
2126
2127 if (!cctx || !requireRdpClientContext(&cctx->context))
2128 return FALSE;
2129
2130#if defined(CHANNEL_RDPEI_CLIENT)
2131 RdpeiClientContext* rdpei = cctx->rdpei;
2132
2133 if (!rdpei)
2134 return freerdp_handle_touch_to_mouse(cctx, FALSE, contact);
2135
2136 int contactId = 0;
2137
2138 if (rdpei->TouchRawEvent)
2139 {
2140 const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP;
2141 const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2142 ? CONTACT_DATA_PRESSURE_PRESENT
2143 : 0;
2144 // Ensure contact position is unchanged from "engaged" to "out of range" state
2145 const UINT rc1 =
2146 rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2147 RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
2148 RDPINPUT_CONTACT_FLAG_INCONTACT,
2149 contactFlags, contact->pressure);
2150 if (rc1 != CHANNEL_RC_OK)
2151 return FALSE;
2152
2153 const UINT rc2 = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y,
2154 &contactId, flags, contactFlags, contact->pressure);
2155 if (rc2 != CHANNEL_RC_OK)
2156 return FALSE;
2157 }
2158 else
2159 {
2160 WINPR_ASSERT(rdpei->TouchEnd);
2161 const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
2162 if (rc != CHANNEL_RC_OK)
2163 return FALSE;
2164 }
2165 return TRUE;
2166#else
2167 WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2168 "-DCHANNEL_RDPEI_CLIENT=ON");
2169 return freerdp_handle_touch_to_mouse(cctx, FALSE, contact);
2170#endif
2171}
2172
2173WINPR_ATTR_NODISCARD
2174static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2175{
2176 WINPR_ASSERT(cctx);
2177 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2178 WINPR_ASSERT(contact);
2179
2180 if (!cctx || !requireRdpClientContext(&cctx->context))
2181 return FALSE;
2182
2183#if defined(CHANNEL_RDPEI_CLIENT)
2184 RdpeiClientContext* rdpei = cctx->rdpei;
2185
2186 // Emulate mouse click if touch is not possible, like in login screen
2187 if (!rdpei)
2188 return freerdp_handle_touch_to_mouse(cctx, TRUE, contact);
2189
2190 int contactId = 0;
2191
2192 if (rdpei->TouchRawEvent)
2193 {
2194 const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
2195 RDPINPUT_CONTACT_FLAG_INCONTACT;
2196 const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2197 ? CONTACT_DATA_PRESSURE_PRESENT
2198 : 0;
2199 const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2200 flags, contactFlags, contact->pressure);
2201 if (rc != CHANNEL_RC_OK)
2202 return FALSE;
2203 }
2204 else
2205 {
2206 WINPR_ASSERT(rdpei->TouchBegin);
2207 const UINT rc = rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId);
2208 if (rc != CHANNEL_RC_OK)
2209 return FALSE;
2210 }
2211
2212 return TRUE;
2213#else
2214 WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2215 "-DCHANNEL_RDPEI_CLIENT=ON");
2216 return freerdp_handle_touch_to_mouse(cctx, TRUE, contact);
2217#endif
2218}
2219
2220WINPR_ATTR_NODISCARD
2221static BOOL freerdp_handle_touch_motion_to_mouse(rdpClientContext* cctx,
2222 const FreeRDP_TouchContact* contact)
2223{
2224 WINPR_ASSERT(cctx);
2225 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2226
2227 if (!cctx || !requireRdpClientContext(&cctx->context))
2228 return FALSE;
2229
2230 const UINT16 flags = PTR_FLAGS_MOVE;
2231
2232 WINPR_ASSERT(contact);
2233 WINPR_ASSERT(contact->x <= UINT16_MAX);
2234 WINPR_ASSERT(contact->y <= UINT16_MAX);
2235 return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
2236}
2237
2238WINPR_ATTR_NODISCARD
2239static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2240{
2241 WINPR_ASSERT(cctx);
2242 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2243 WINPR_ASSERT(contact);
2244
2245 if (!cctx || !requireRdpClientContext(&cctx->context))
2246 return FALSE;
2247
2248#if defined(CHANNEL_RDPEI_CLIENT)
2249 RdpeiClientContext* rdpei = cctx->rdpei;
2250
2251 if (!rdpei)
2252 return freerdp_handle_touch_motion_to_mouse(cctx, contact);
2253
2254 int contactId = 0;
2255
2256 if (rdpei->TouchRawEvent)
2257 {
2258 const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
2259 RDPINPUT_CONTACT_FLAG_INCONTACT;
2260 const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2261 ? CONTACT_DATA_PRESSURE_PRESENT
2262 : 0;
2263 const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2264 flags, contactFlags, contact->pressure);
2265 if (rc != CHANNEL_RC_OK)
2266 return FALSE;
2267 }
2268 else
2269 {
2270 WINPR_ASSERT(rdpei->TouchUpdate);
2271 const UINT rc = rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId);
2272 if (rc != CHANNEL_RC_OK)
2273 return FALSE;
2274 }
2275
2276 return TRUE;
2277#else
2278 WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2279 "-DCHANNEL_RDPEI_CLIENT=ON");
2280 return freerdp_handle_touch_motion_to_mouse(cctx, contact);
2281#endif
2282}
2283
2284WINPR_ATTR_NODISCARD
2285static BOOL freerdp_handle_touch_cancel(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2286{
2287 WINPR_ASSERT(cctx);
2288 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2289 WINPR_ASSERT(contact);
2290
2291 if (!cctx || !requireRdpClientContext(&cctx->context))
2292 return FALSE;
2293
2294#if defined(CHANNEL_RDPEI_CLIENT)
2295 RdpeiClientContext* rdpei = cctx->rdpei;
2296
2297 if (!rdpei)
2298 return freerdp_handle_touch_to_mouse(cctx, false, contact);
2299
2300 int contactId = 0;
2301
2302 if (rdpei->TouchRawEvent)
2303 {
2304 const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED;
2305 const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2306 ? CONTACT_DATA_PRESSURE_PRESENT
2307 : 0;
2308 const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2309 flags, contactFlags, contact->pressure);
2310 if (rc != CHANNEL_RC_OK)
2311 return FALSE;
2312 }
2313 else
2314 {
2315 WINPR_ASSERT(rdpei->TouchUpdate);
2316 const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
2317 if (rc != CHANNEL_RC_OK)
2318 return FALSE;
2319 }
2320
2321 return TRUE;
2322#else
2323 WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2324 "-DCHANNEL_RDPEI_CLIENT=ON");
2325 return freerdp_handle_touch_to_mouse(cctx, false, contact);
2326#endif
2327}
2328
2329WINPR_ATTR_NODISCARD
2330static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId,
2331 UINT32 pressure, INT32 x, INT32 y,
2332 FreeRDP_TouchContact* pcontact)
2333{
2334 WINPR_ASSERT(cctx);
2335 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2336 WINPR_ASSERT(pcontact);
2337
2338 if (!cctx || !requireRdpClientContext(&cctx->context))
2339 return FALSE;
2340
2341 for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++)
2342 {
2343 FreeRDP_TouchContact* contact = &cctx->contacts[i];
2344
2345 const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0));
2346 if (newcontact || (contact->id == touchId))
2347 {
2348 contact->id = touchId;
2349 contact->flags = flags;
2350 contact->pressure = pressure;
2351 contact->x = x;
2352 contact->y = y;
2353
2354 *pcontact = *contact;
2355
2356 const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0;
2357 if (resetcontact)
2358 {
2359 FreeRDP_TouchContact empty = WINPR_C_ARRAY_INIT;
2360 *contact = empty;
2361 }
2362 return TRUE;
2363 }
2364 }
2365
2366 return FALSE;
2367}
2368
2369BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
2370 UINT32 pressure, INT32 x, INT32 y)
2371{
2372 WINPR_ASSERT(cctx);
2373 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2374
2375 if (!cctx || !requireRdpClientContext(&cctx->context))
2376 return FALSE;
2377
2378 const UINT32 mask =
2379 FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION | FREERDP_TOUCH_CANCEL;
2380 WINPR_ASSERT(cctx);
2381
2382 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2383 if (state != CONNECTION_STATE_ACTIVE)
2384 return TRUE;
2385
2386 FreeRDP_TouchContact contact = WINPR_C_ARRAY_INIT;
2387
2388 if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact))
2389 return FALSE;
2390
2391 switch (flags & mask)
2392 {
2393 case FREERDP_TOUCH_DOWN:
2394 return freerdp_handle_touch_down(cctx, &contact);
2395 case FREERDP_TOUCH_UP:
2396 return freerdp_handle_touch_up(cctx, &contact);
2397 case FREERDP_TOUCH_MOTION:
2398 return freerdp_handle_touch_motion(cctx, &contact);
2399 case FREERDP_TOUCH_CANCEL:
2400 return freerdp_handle_touch_cancel(cctx, &contact);
2401 default:
2402 WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %" PRIu32 ", ignoring", flags);
2403 return FALSE;
2404 }
2405}
2406
2407BOOL freerdp_client_load_channels(freerdp* instance)
2408{
2409 WINPR_ASSERT(instance);
2410 WINPR_ASSERT(instance->context);
2411
2412 if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings))
2413 {
2414 WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError());
2415 return FALSE;
2416 }
2417 return TRUE;
2418}
2419
2420int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
2421{
2422 const char* str_data = freerdp_get_logon_error_info_data(data);
2423 const char* str_type = freerdp_get_logon_error_info_type(type);
2424
2425 if (!instance || !instance->context)
2426 return -1;
2427
2428 WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
2429 return 1;
2430}
2431
2432WINPR_ATTR_NODISCARD
2433static FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid,
2434 size_t* pos)
2435{
2436 WINPR_ASSERT(cctx);
2437 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2438
2439 if (!cctx || !requireRdpClientContext(&cctx->context))
2440 return nullptr;
2441
2442 for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
2443 {
2444 FreeRDP_PenDevice* pen = &cctx->pens[i];
2445 if (deviceid == pen->deviceid)
2446 {
2447 if (pos)
2448 *pos = i;
2449 return pen;
2450 }
2451 }
2452 return nullptr;
2453}
2454
2455WINPR_ATTR_NODISCARD
2456static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
2457 double pressure)
2458{
2459 static const INT32 null_deviceid = 0;
2460
2461 WINPR_ASSERT(cctx);
2462 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2463 WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0);
2464
2465 if (!cctx || !requireRdpClientContext(&cctx->context))
2466 return FALSE;
2467
2468 if (freerdp_client_is_pen(cctx, deviceid))
2469 {
2470 WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid);
2471 return FALSE;
2472 }
2473
2474 size_t pos = 0;
2475 FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos);
2476 if (pen)
2477 {
2478 const FreeRDP_PenDevice empty = WINPR_C_ARRAY_INIT;
2479 *pen = empty;
2480
2481 pen->deviceid = deviceid;
2482 pen->max_pressure = pressure;
2483 pen->flags = flags;
2484
2485 WLog_DBG(TAG, "registered pen at index %" PRIuz, pos);
2486 return TRUE;
2487 }
2488
2489 WLog_WARN(TAG, "No free slot for an additional pen device, skipping");
2490 return TRUE;
2491}
2492
2493BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...)
2494{
2495 WINPR_ASSERT(cctx);
2496 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2497
2498 if (!cctx || !requireRdpClientContext(&cctx->context))
2499 return FALSE;
2500
2501 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2502 if (state != CONNECTION_STATE_ACTIVE)
2503 return TRUE;
2504
2505#if defined(CHANNEL_RDPEI_CLIENT)
2506 if ((flags & FREERDP_PEN_REGISTER) != 0)
2507 {
2508 va_list args = WINPR_C_ARRAY_INIT;
2509
2510 va_start(args, deviceid);
2511 double pressure = va_arg(args, double);
2512 va_end(args);
2513 return freerdp_client_register_pen(cctx, flags, deviceid, pressure);
2514 }
2515 size_t pos = 0;
2516 FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos);
2517 if (!pen)
2518 {
2519 WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags);
2520 return FALSE;
2521 }
2522
2523 UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT;
2524 UINT32 penFlags =
2525 ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0;
2526
2527 RdpeiClientContext* rdpei = cctx->rdpei;
2528 WINPR_ASSERT(rdpei);
2529
2530 UINT32 normalizedpressure = 1024;
2531 INT32 x = 0;
2532 INT32 y = 0;
2533 UINT16 rotation = 0;
2534 INT16 tiltX = 0;
2535 INT16 tiltY = 0;
2536 va_list args = WINPR_C_ARRAY_INIT;
2537 va_start(args, deviceid);
2538
2539 x = va_arg(args, INT32);
2540 y = va_arg(args, INT32);
2541 if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0)
2542 {
2543 const double pressure = va_arg(args, double);
2544 const double np = (pressure * 1024.0) / pen->max_pressure;
2545 normalizedpressure = (UINT32)lround(np);
2546 WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure);
2547 fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
2548 }
2549 if ((flags & FREERDP_PEN_HAS_ROTATION) != 0)
2550 {
2551 const unsigned arg = va_arg(args, unsigned);
2552 rotation = WINPR_ASSERTING_INT_CAST(UINT16, arg);
2553 fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT;
2554 }
2555 if ((flags & FREERDP_PEN_HAS_TILTX) != 0)
2556 {
2557 const int arg = va_arg(args, int);
2558 tiltX = WINPR_ASSERTING_INT_CAST(INT16, arg);
2559 fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT;
2560 }
2561 if ((flags & FREERDP_PEN_HAS_TILTY) != 0)
2562 {
2563 const int arg = va_arg(args, int);
2564 tiltY = WINPR_ASSERTING_INT_CAST(INT16, arg);
2565 fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT;
2566 }
2567 va_end(args);
2568
2569 if ((flags & FREERDP_PEN_PRESS) != 0)
2570 {
2571 // Ensure that only one button is pressed
2572 if (pen->pressed)
2573 flags = FREERDP_PEN_MOTION |
2574 (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED));
2575 else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
2576 pen->flags |= FREERDP_PEN_BARREL_PRESSED;
2577 }
2578 else if ((flags & FREERDP_PEN_RELEASE) != 0)
2579 {
2580 if (!pen->pressed ||
2581 ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED)))
2582 flags = FREERDP_PEN_MOTION |
2583 (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED));
2584 else
2585 pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED;
2586 }
2587
2588 flags |= pen->flags;
2589 if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0)
2590 penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED;
2591 if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
2592 penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED;
2593
2594 pen->last_x = x;
2595 pen->last_y = y;
2596 if ((flags & FREERDP_PEN_PRESS) != 0)
2597 {
2598 WLog_DBG(TAG, "Pen press %" PRId32, deviceid);
2599 pen->hovering = FALSE;
2600 pen->pressed = TRUE;
2601
2602 WINPR_ASSERT(rdpei->PenBegin);
2603 const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags,
2604 normalizedpressure, rotation, tiltX, tiltY);
2605 return rc == CHANNEL_RC_OK;
2606 }
2607 else if ((flags & FREERDP_PEN_MOTION) != 0)
2608 {
2609 UINT rc = ERROR_INTERNAL_ERROR;
2610 if (pen->pressed)
2611 {
2612 WLog_DBG(TAG, "Pen update %" PRId32, deviceid);
2613
2614 // TODO: what if no rotation is supported but tilt is?
2615 WINPR_ASSERT(rdpei->PenUpdate);
2616 rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure,
2617 rotation, tiltX, tiltY);
2618 }
2619 else if (pen->hovering)
2620 {
2621 WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid);
2622
2623 WINPR_ASSERT(rdpei->PenHoverUpdate);
2624 rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2625 penFlags, normalizedpressure, rotation, tiltX, tiltY);
2626 }
2627 else
2628 {
2629 WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid);
2630 pen->hovering = TRUE;
2631
2632 WINPR_ASSERT(rdpei->PenHoverBegin);
2633 rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2634 penFlags, normalizedpressure, rotation, tiltX, tiltY);
2635 }
2636 return rc == CHANNEL_RC_OK;
2637 }
2638 else if ((flags & FREERDP_PEN_RELEASE) != 0)
2639 {
2640 WLog_DBG(TAG, "Pen release %" PRId32, deviceid);
2641 pen->pressed = FALSE;
2642 pen->hovering = TRUE;
2643
2644 WINPR_ASSERT(rdpei->PenUpdate);
2645 const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags,
2646 normalizedpressure, rotation, tiltX, tiltY);
2647 if (rc != CHANNEL_RC_OK)
2648 return FALSE;
2649 WINPR_ASSERT(rdpei->PenEnd);
2650 const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2651 penFlags, normalizedpressure, rotation, tiltX, tiltY);
2652 return re == CHANNEL_RC_OK;
2653 }
2654
2655 WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags);
2656#else
2657 WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with "
2658 "-DCHANNEL_RDPEI_CLIENT=ON");
2659#endif
2660
2661 return FALSE;
2662}
2663
2664BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx)
2665{
2666 WINPR_ASSERT(cctx);
2667 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2668
2669 if (!cctx || !requireRdpClientContext(&cctx->context))
2670 return FALSE;
2671
2672 const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2673 if (state != CONNECTION_STATE_ACTIVE)
2674 return TRUE;
2675
2676#if defined(CHANNEL_RDPEI_CLIENT)
2677 RdpeiClientContext* rdpei = cctx->rdpei;
2678
2679 if (!rdpei)
2680 return FALSE;
2681
2682 for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
2683 {
2684 FreeRDP_PenDevice* pen = &cctx->pens[i];
2685 if (pen->hovering)
2686 {
2687 WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid);
2688 pen->hovering = FALSE;
2689 const UINT rc =
2690 rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y);
2691 if (rc != CHANNEL_RC_OK)
2692 return FALSE;
2693 }
2694 }
2695 return TRUE;
2696#else
2697 WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with "
2698 "-DCHANNEL_RDPEI_CLIENT=ON");
2699 return FALSE;
2700#endif
2701}
2702
2703BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid)
2704{
2705 WINPR_ASSERT(cctx);
2706 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2707
2708 if (!cctx || !requireRdpClientContext(&cctx->context))
2709 return FALSE;
2710
2711 if (deviceid == 0)
2712 return FALSE;
2713
2714 for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++)
2715 {
2716 const FreeRDP_PenDevice* pen = &cctx->pens[x];
2717 if (pen->deviceid == deviceid)
2718 return TRUE;
2719 }
2720
2721 return FALSE;
2722}
2723
2724BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* cctx)
2725{
2726 WINPR_ASSERT(cctx);
2727 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2728
2729 if (!cctx || !requireRdpClientContext(&cctx->context))
2730 return FALSE;
2731
2732 const rdpSettings* settings = cctx->context.settings;
2733 const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove);
2734 const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent);
2735 BOOL ainput = FALSE;
2736#if defined(CHANNEL_AINPUT_CLIENT)
2737 ainput = cctx->ainput != nullptr;
2738#endif
2739
2740 return useRelative && (haveRelative || ainput);
2741}
2742
2743#if defined(WITH_AAD)
2744WINPR_ATTR_MALLOC(free, 1)
2745static char* get_redirect_uri(const rdpSettings* settings)
2746{
2747 char* redirect_uri = nullptr;
2748 const bool cli = freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks);
2749 if (cli)
2750 {
2751 const char* redirect_fmt =
2752 freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessAadFormat);
2753 const BOOL useTenant = freerdp_settings_get_bool(settings, FreeRDP_GatewayAvdUseTenantid);
2754 const char* tenantid = "common";
2755 if (useTenant)
2756 tenantid = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAadtenantid);
2757
2758 if (tenantid && redirect_fmt)
2759 {
2760 const char* url =
2761 freerdp_settings_get_string(settings, FreeRDP_GatewayAzureActiveDirectory);
2762
2763 size_t redirect_len = 0;
2764 winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, url, tenantid);
2765 }
2766 }
2767 else
2768 {
2769 const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2770 const char* redirect_fmt =
2771 freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessTokenFormat);
2772
2773 size_t redirect_len = 0;
2774 winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, client_id);
2775 }
2776 return redirect_uri;
2777}
2778
2781WINPR_ATTR_MALLOC(free, 1)
2782static char* get_login_hint(const rdpSettings* settings, FreeRDP_Settings_Keys_String primary,
2783 FreeRDP_Settings_Keys_String secondary)
2784{
2785 const FreeRDP_Settings_Keys_String keys[] = { primary, secondary };
2786 for (size_t x = 0; x < ARRAYSIZE(keys); x++)
2787 {
2788 const char* user = freerdp_settings_get_string(settings, keys[x]);
2789 /* AAD accounts are UPNs: a DOMAIN\user or bare name would only prefill the form with an
2790 * account that can't sign in */
2791 if (!user || !strchr(user, '@') || strchr(user, '\\'))
2792 continue;
2793
2794 char* encoded = winpr_str_url_encode(user, strlen(user));
2795 if (!encoded)
2796 return nullptr;
2797
2798 char* param = nullptr;
2799 size_t len = 0;
2800 winpr_asprintf(&param, &len, "&login_hint=%s", encoded);
2801 free(encoded);
2802 return param;
2803 }
2804 return _strdup("");
2805}
2806
2807WINPR_ATTR_MALLOC(free, 1)
2808static char* avd_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
2809{
2810 WINPR_ASSERT(cctx);
2811 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2812 WINPR_ASSERT(plen);
2813 *plen = 0;
2814
2815 if (!cctx || !requireRdpClientContext(&cctx->context))
2816 return nullptr;
2817
2818 const rdpSettings* settings = cctx->context.settings;
2819 const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2820 const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
2821 AAD_WELLKNOWN_authorization_endpoint);
2822 const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope);
2823
2824 if (!client_id || !ep || !scope)
2825 return nullptr;
2826
2827 char* redirect_uri = get_redirect_uri(settings);
2828 /* the AVD token is for the gateway: prefer the gateway user */
2829 char* login_hint = get_login_hint(settings, FreeRDP_GatewayUsername, FreeRDP_Username);
2830 char* url = nullptr;
2831 if (redirect_uri && login_hint)
2832 winpr_asprintf(&url, plen, "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s%s",
2833 ep, client_id, scope, redirect_uri, login_hint);
2834 free(login_hint);
2835 free(redirect_uri);
2836 return url;
2837}
2838
2839WINPR_ATTR_MALLOC(free, 1)
2840static char* avd_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
2841{
2842 WINPR_ASSERT(cctx);
2843 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2844 WINPR_ASSERT(plen);
2845 *plen = 0;
2846
2847 if (!cctx || !requireRdpClientContext(&cctx->context))
2848 return nullptr;
2849
2850 const rdpSettings* settings = cctx->context.settings;
2851 const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2852 const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
2853 AAD_WELLKNOWN_authorization_endpoint);
2854 const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope);
2855
2856 if (!client_id || !ep || !scope)
2857 return nullptr;
2858
2859 char* redirect_uri = get_redirect_uri(settings);
2860 if (!redirect_uri)
2861 return nullptr;
2862
2863 char* url = nullptr;
2864
2865 const char* code = va_arg(ap, const char*);
2866 winpr_asprintf(&url, plen,
2867 "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s",
2868 code, client_id, scope, redirect_uri);
2869 free(redirect_uri);
2870 return url;
2871}
2872
2873WINPR_ATTR_MALLOC(free, 1)
2874static char* aad_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
2875{
2876 WINPR_ASSERT(cctx);
2877 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2878 WINPR_ASSERT(plen);
2879
2880 *plen = 0;
2881 if (!cctx || !requireRdpClientContext(&cctx->context))
2882 return nullptr;
2883
2884 const rdpSettings* settings = cctx->context.settings;
2885 char* url = nullptr;
2886 char* redirect_uri = get_redirect_uri(settings);
2887 /* the RDSTLS token is for the session host: prefer the host user */
2888 char* login_hint = get_login_hint(settings, FreeRDP_Username, FreeRDP_GatewayUsername);
2889
2890 const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2891 if (!client_id || !redirect_uri || !login_hint)
2892 goto cleanup;
2893
2894 {
2895 const char* scope = va_arg(ap, const char*);
2896 if (!scope)
2897 goto cleanup;
2898
2899 {
2900 const char* ep = freerdp_utils_aad_get_wellknown_string(
2901 &cctx->context, AAD_WELLKNOWN_authorization_endpoint);
2902 winpr_asprintf(&url, plen,
2903 "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s%s", ep,
2904 client_id, scope, redirect_uri, login_hint);
2905 }
2906 }
2907
2908cleanup:
2909 free(login_hint);
2910 free(redirect_uri);
2911 return url;
2912}
2913
2914WINPR_ATTR_MALLOC(free, 1)
2915static char* aad_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
2916{
2917 WINPR_ASSERT(cctx);
2918 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2919 WINPR_ASSERT(plen);
2920 *plen = 0;
2921
2922 if (!cctx || !requireRdpClientContext(&cctx->context))
2923 return nullptr;
2924
2925 const rdpSettings* settings = cctx->context.settings;
2926 const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2927 const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
2928 AAD_WELLKNOWN_authorization_endpoint);
2929 const char* scope = va_arg(ap, const char*);
2930 const char* code = va_arg(ap, const char*);
2931 const char* req_cnf = va_arg(ap, const char*);
2932
2933 if (!client_id || !ep || !scope || !code || !req_cnf)
2934 return nullptr;
2935
2936 char* redirect_uri = get_redirect_uri(settings);
2937 if (!redirect_uri)
2938 return nullptr;
2939
2940 char* url = nullptr;
2941
2942 winpr_asprintf(
2943 &url, plen,
2944 "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s&req_cnf=%s",
2945 code, client_id, scope, redirect_uri, req_cnf);
2946 free(redirect_uri);
2947 return url;
2948}
2949#endif
2950
2951char* freerdp_client_get_aad_url(rdpClientContext* cctx, freerdp_client_aad_type type, ...)
2952{
2953 WINPR_ASSERT(cctx);
2954 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2955
2956 if (!cctx || !requireRdpClientContext(&cctx->context))
2957 return nullptr;
2958
2959 char* str = nullptr;
2960 size_t len = 0;
2961
2962 va_list ap = WINPR_C_ARRAY_INIT;
2963 va_start(ap, type);
2964 switch (type)
2965 {
2966#if defined(WITH_AAD)
2967 case FREERDP_CLIENT_AAD_AUTH_REQUEST:
2968 if (freerdp_oauth2_reset(cctx->oauth2))
2969 str = aad_auth_request(cctx, ap, &len);
2970 break;
2971 case FREERDP_CLIENT_AAD_TOKEN_REQUEST:
2972 str = aad_token_request(cctx, ap, &len);
2973 break;
2974 case FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST:
2975 if (freerdp_oauth2_reset(cctx->oauth2))
2976 str = avd_auth_request(cctx, ap, &len);
2977 break;
2978 case FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST:
2979 str = avd_token_request(cctx, ap, &len);
2980 break;
2981#endif
2982 default:
2983 break;
2984 }
2985 va_end(ap);
2986
2987 char* safestr = freerdp_oauth2_append_state(cctx->oauth2, str, len, nullptr);
2988 winpr_zfree(str);
2989 return safestr;
2990}
2991
2992BOOL client_common_save_session_info(WINPR_ATTR_UNUSED rdpContext* context, UINT32 type,
2993 const void* data)
2994{
2995 char buffer[128] = WINPR_C_ARRAY_INIT;
2996 WLog_INFO(TAG, "%s [%s]", freerdp_session_logon_type_str(type),
2997 freerdp_session_logon_type_data_str(type, data, buffer, sizeof(buffer)));
2998 return TRUE;
2999}
3000
3001char* freerdp_client_extract_aad_code(rdpClientContext* cctx, const char* data, size_t length)
3002{
3003 WINPR_ASSERT(cctx);
3004 WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3005
3006 if (!cctx || !requireRdpClientContext(&cctx->context))
3007 return nullptr;
3008 return freerdp_oauth2_extract_code(cctx->oauth2, data, length);
3009}
3010
3011WINPR_ATTR_NODISCARD
3012static BOOL client_helper_get_access_token_va(freerdp* instance,
3013 WINPR_ATTR_UNUSED AccessTokenType tokenType,
3014 WINPR_ATTR_UNUSED char** token,
3015 WINPR_ATTR_UNUSED size_t count, va_list ap)
3016{
3017 WINPR_ASSERT(instance);
3018 WINPR_ASSERT(token);
3019
3020 *token = nullptr;
3021
3022 WINPR_ASSERT(requireRdpClientContext(instance->context));
3023
3024#if defined(WITH_AAD)
3025 rdpClientContext* cctx = (rdpClientContext*)instance->context;
3026 WINPR_ASSERT(cctx);
3027
3028 /* Keep one helper for the whole client lifetime (it is stopped when the context is freed):
3029 * its browser session then carries over from one token request to the next, e.g. AVD
3030 * gateway then session host, instead of prompting for a login each time. Only start a new
3031 * one if there's none yet or the previous one exited. */
3032 if (!aad_auth_helper_is_running(cctx->aadHelper))
3033 {
3034 aad_auth_helper_stop(cctx->aadHelper);
3035 cctx->aadHelper = aad_auth_helper_start(cctx);
3036 if (!cctx->aadHelper)
3037 return FALSE;
3038 }
3039
3040 const BOOL rc =
3041 aad_auth_helper_get_access_token_v(cctx->aadHelper, tokenType, token, count, ap);
3042
3043 /* a helper that died during the request (crash, user killed it) is restarted next time */
3044 if (!rc && !aad_auth_helper_is_running(cctx->aadHelper))
3045 {
3046 aad_auth_helper_stop(cctx->aadHelper);
3047 cctx->aadHelper = nullptr;
3048 }
3049 return rc;
3050#else
3051 WLog_ERR(TAG, "Build does not support AAD authentication");
3052 return FALSE;
3053#endif
3054}
3055
3056BOOL client_failsafe_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
3057 size_t count, ...)
3058{
3059 {
3060 va_list ap = WINPR_C_ARRAY_INIT;
3061 va_start(ap, count);
3062 const BOOL rc = client_helper_get_access_token_va(instance, tokenType, token, count, ap);
3063 va_end(ap);
3064 if (rc)
3065 return rc;
3066 }
3067 {
3068 va_list ap = WINPR_C_ARRAY_INIT;
3069 va_start(ap, count);
3070 const BOOL rc = client_cli_get_access_token_va(instance, tokenType, token, count, ap);
3071 va_end(ap);
3072 return rc;
3073 }
3074}
3075
3076BOOL client_helper_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
3077 size_t count, ...)
3078{
3079 va_list ap = WINPR_C_ARRAY_INIT;
3080 va_start(ap, count);
3081 const BOOL rc = client_helper_get_access_token_va(instance, tokenType, token, count, ap);
3082 va_end(ap);
3083 return rc;
3084}
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 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.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.