FreeRDP
Loading...
Searching...
No Matches
SDL3/dialogs/sdl_dialogs.cpp
1
20#include <vector>
21#include <string>
22#include <cassert>
23
24#include <freerdp/log.h>
25#include <freerdp/utils/passphrase.h>
26#include <freerdp/utils/smartcardlogon.h>
27
28#include <SDL3/SDL.h>
29
30#include "../sdl_context.hpp"
31#include "sdl_dialogs.hpp"
32#include "sdl_input_widget_pair.hpp"
33#include "sdl_input_widget_pair_list.hpp"
34#include "sdl_select.hpp"
35#include "sdl_select_list.hpp"
36#include "sdl_connection_dialog.hpp"
37
38enum
39{
40 SHOW_DIALOG_ACCEPT_REJECT = 1,
41 SHOW_DIALOG_TIMED_ACCEPT = 2
42};
43
44static const char* type_str_for_flags(UINT32 flags)
45{
46 const char* type = "RDP-Server";
47
48 if (flags & VERIFY_CERT_FLAG_GATEWAY)
49 type = "RDP-Gateway";
50
51 if (flags & VERIFY_CERT_FLAG_REDIRECT)
52 type = "RDP-Redirect";
53 return type;
54}
55
56static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result)
57{
58 const SDL_Event empty = {};
59
60 WINPR_ASSERT(context);
61 WINPR_ASSERT(result);
62
63 while (!freerdp_shall_disconnect_context(context))
64 {
65 *result = empty;
66 const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type);
67 if (rc > 0)
68 return TRUE;
69 Sleep(1);
70 }
71 return FALSE;
72}
73
74static int sdl_show_dialog(rdpContext* context, const char* title, const char* message,
75 Sint32 flags)
76{
77 SDL_Event event = {};
78
79 if (!sdl_push_user_event(SDL_EVENT_USER_SHOW_DIALOG, title, message, flags))
80 return 0;
81
82 if (!sdl_wait_for_result(context, SDL_EVENT_USER_SHOW_RESULT, &event))
83 return 0;
84
85 return event.user.code;
86}
87
88BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
89 rdp_auth_reason reason)
90{
91 SDL_Event event = {};
92 BOOL res = FALSE;
93
94 const char* target = freerdp_settings_get_server_name(instance->context->settings);
95 switch (reason)
96 {
97 case AUTH_RDSTLS:
98 case AUTH_NLA:
99 break;
100
101 case AUTH_TLS:
102 case AUTH_RDP:
103 case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
104 case AUTH_FIDO_PIN:
105 if ((*username) && (*password))
106 return TRUE;
107 break;
108 case GW_AUTH_HTTP:
109 case GW_AUTH_RDG:
110 case GW_AUTH_RPC:
111 target =
112 freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname);
113 break;
114 default:
115 break;
116 }
117
118 char* title = nullptr;
119 size_t titlesize = 0;
120 winpr_asprintf(&title, &titlesize, "Credentials required for %s", target);
121
122 CStringPtr guard(title, free);
123 char* u = nullptr;
124 char* d = nullptr;
125 char* p = nullptr;
126
127 assert(username);
128 assert(domain);
129 assert(password);
130
131 u = *username;
132 d = *domain;
133 p = *password;
134
135 auto sdl = get_context(instance);
136 if (!p && !sdl->credentialsRead())
137 {
138 std::string line;
139 line.reserve(1024);
140
141 const BOOL fromStdin =
142 freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
143 auto env =
144 freerdp_passphrase_from_env(instance->context, title, line.data(), line.capacity());
145 if (!env && fromStdin)
146 env = freerdp_passphrase_read(instance->context, title, line.data(), line.capacity(),
147 fromStdin);
148 if (env)
149 p = _strdup(env);
150
151 *password = p;
152 if (p)
153 {
154 sdl->setCredentialsRead();
155 if (u)
156 return TRUE;
157 }
158 }
159
160 if (!sdl_push_user_event(SDL_EVENT_USER_AUTH_DIALOG, title, u, d, p, reason))
161 return res;
162
163 if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_AUTH_RESULT, &event))
164 return res;
165
166 auto arg = reinterpret_cast<SDL_UserAuthArg*>(event.padding);
167
168 res = arg->result > 0;
169
170 free(*username);
171 free(*domain);
172 free(*password);
173 *username = arg->user;
174 *domain = arg->domain;
175 *password = arg->password;
176
177 return res;
178}
179
180BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
181 DWORD* choice, BOOL gateway)
182{
183 BOOL res = FALSE;
184
185 WINPR_ASSERT(instance);
186 WINPR_ASSERT(cert_list);
187 WINPR_ASSERT(choice);
188
189 std::vector<std::string> strlist;
190 std::vector<const char*> list;
191 for (DWORD i = 0; i < count; i++)
192 {
193 const SmartcardCertInfo* cert = cert_list[i];
194 char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
195 char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
196
197 char* msg = nullptr;
198 size_t len = 0;
199
200 winpr_asprintf(&msg, &len,
201 "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
202 container_name, reader, cert->userHint, cert->domainHint, cert->subject,
203 cert->issuer, cert->upn);
204
205 strlist.emplace_back(msg);
206 free(msg);
207 free(reader);
208 free(container_name);
209
210 auto& m = strlist.back();
211 list.push_back(m.c_str());
212 }
213
214 SDL_Event event = {};
215 const char* title = "Select a logon smartcard certificate";
216 if (gateway)
217 title = "Select a gateway logon smartcard certificate";
218 if (!sdl_push_user_event(SDL_EVENT_USER_SCARD_DIALOG, title, list.data(), count))
219 return res;
220
221 if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_SCARD_RESULT, &event))
222 return res;
223
224 res = (event.user.code >= 0);
225 *choice = static_cast<DWORD>(event.user.code);
226
227 return res;
228}
229
230SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
231 [[maybe_unused]] void* userarg)
232{
233 WINPR_ASSERT(instance);
234 WINPR_ASSERT(instance->context);
235 WINPR_ASSERT(what);
236
237 auto sdl = get_context(instance->context);
238 auto settings = instance->context->settings;
239 const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
240 const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
241
242 sdl->getDialog().setTitle("Retry connection to %s",
243 freerdp_settings_get_server_name(instance->context->settings));
244
245 if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
246 {
247 sdl->getDialog().showError("Unknown module %s, aborting", what);
248 return -1;
249 }
250
251 if (current == 0)
252 {
253 if (strcmp(what, "arm-transport") == 0)
254 sdl->getDialog().showWarn("[%s] Starting your VM. It may take up to 5 minutes", what);
255 }
256
257 if (!enabled)
258 {
259 sdl->getDialog().showError(
260 "Automatic reconnection disabled, terminating. Try to connect again later");
261 return -1;
262 }
263
264 const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
265 if (current >= max)
266 {
267 sdl->getDialog().showError(
268 "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
269 "tech support for help if this keeps happening.",
270 what);
271 return -1;
272 }
273
274 sdl->getDialog().showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
275 "ms before next attempt",
276 what, current + 1, max, delay);
277 return WINPR_ASSERTING_INT_CAST(ssize_t, delay);
278}
279
280BOOL sdl_present_gateway_message(freerdp* instance, [[maybe_unused]] UINT32 type,
281 BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length,
282 const WCHAR* wmessage)
283{
284 if (!isDisplayMandatory)
285 return TRUE;
286
287 char* title = nullptr;
288 size_t len = 0;
289 winpr_asprintf(&title, &len, "[gateway]");
290
291 Sint32 flags = 0;
292 if (isConsentMandatory)
293 flags = SHOW_DIALOG_ACCEPT_REJECT;
294 else if (isDisplayMandatory)
295 flags = SHOW_DIALOG_TIMED_ACCEPT;
296 char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
297
298 const int rc = sdl_show_dialog(instance->context, title, message, flags);
299 free(title);
300 free(message);
301 return rc > 0;
302}
303
304int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
305{
306 if (!instance || !instance->context)
307 return -1;
308
309 /* ignore LOGON_MSG_SESSION_CONTINUE message */
310 switch (type)
311 {
312 case LOGON_MSG_SESSION_CONTINUE:
313 return 0;
314 default:
315 break;
316 }
317
318 const char* str_data = freerdp_get_logon_error_info_data(data);
319 const char* str_type = freerdp_get_logon_error_info_type(type);
320 /* The server shows its own logon UI: show the desktop, a local dialog would block it. */
321 auto sdl = get_context(instance);
322 sdl->getRailChannelContext().noteLogonPrompt();
323 WLog_Print(sdl->getWLog(), WLOG_INFO, "Logon Error Info %s [%s]", str_data, str_type);
324 return 1;
325}
326
327static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
328 const char* message)
329{
330 if (!sdl_push_user_event(SDL_EVENT_USER_CERT_DIALOG, title, message))
331 return 0;
332
333 SDL_Event event = {};
334 if (!sdl_wait_for_result(context, SDL_EVENT_USER_CERT_RESULT, &event))
335 return 0;
336 return static_cast<DWORD>(event.user.code);
337}
338
339static char* sdl_pem_cert(const char* pem)
340{
341 rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
342 if (!cert)
343 return nullptr;
344
345 char* fp = freerdp_certificate_get_fingerprint(cert);
346 char* start = freerdp_certificate_get_validity(cert, TRUE);
347 char* end = freerdp_certificate_get_validity(cert, FALSE);
348 freerdp_certificate_free(cert);
349
350 char* str = nullptr;
351 size_t slen = 0;
352 winpr_asprintf(&str, &slen,
353 "Valid from: %s\n"
354 "Valid to: %s\n"
355 "Thumbprint: %s\n",
356 start, end, fp);
357 free(fp);
358 free(start);
359 free(end);
360 return str;
361}
362
363DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
364 const char* common_name, const char* subject,
365 const char* issuer, const char* new_fingerprint,
366 const char* old_subject, const char* old_issuer,
367 const char* old_fingerprint, DWORD flags)
368{
369 const char* type = type_str_for_flags(flags);
370
371 WINPR_ASSERT(instance);
372 WINPR_ASSERT(instance->context);
373 WINPR_ASSERT(instance->context->settings);
374
375 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
376 * FreeRDP_CertificateCallbackPreferPEM to TRUE
377 */
378 char* new_fp_str = nullptr;
379 size_t len = 0;
380 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
381 new_fp_str = sdl_pem_cert(new_fingerprint);
382 else
383 winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
384
385 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
386 * FreeRDP_CertificateCallbackPreferPEM to TRUE
387 */
388 char* old_fp_str = nullptr;
389 size_t olen = 0;
390 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
391 old_fp_str = sdl_pem_cert(old_fingerprint);
392 else
393 winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
394
395 const char* collission_str = "";
396 if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
397 {
398 collission_str =
399 "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
400 "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
401 "The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
402 "All manually accepted certificates must be reconfirmed!\n"
403 "\n";
404 }
405
406 char* title = nullptr;
407 size_t tlen = 0;
408 winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
409 type);
410
411 char* message = nullptr;
412 size_t mlen = 0;
413 winpr_asprintf(&message, &mlen,
414 "New Certificate details:\n"
415 "Common Name: %s\n"
416 "Subject: %s\n"
417 "Issuer: %s\n"
418 "%s\n"
419 "Old Certificate details:\n"
420 "Subject: %s\n"
421 "Issuer: %s\n"
422 "%s\n"
423 "%s\n"
424 "The above X.509 certificate does not match the certificate used for previous "
425 "connections.\n"
426 "This may indicate that the certificate has been tampered with.\n"
427 "Please contact the administrator of the RDP server and clarify.\n",
428 common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
429 collission_str);
430
431 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
432 free(title);
433 free(message);
434 free(new_fp_str);
435 free(old_fp_str);
436
437 return rc;
438}
439
440DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
441 const char* common_name, const char* subject, const char* issuer,
442 const char* fingerprint, DWORD flags)
443{
444 const char* type = type_str_for_flags(flags);
445
446 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
447 * FreeRDP_CertificateCallbackPreferPEM to TRUE
448 */
449 char* fp_str = nullptr;
450 size_t len = 0;
451 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
452 fp_str = sdl_pem_cert(fingerprint);
453 else
454 winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
455
456 char* title = nullptr;
457 size_t tlen = 0;
458 winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
459
460 char* message = nullptr;
461 size_t mlen = 0;
462 winpr_asprintf(
463 &message, &mlen,
464 "Common Name: %s\n"
465 "Subject: %s\n"
466 "Issuer: %s\n"
467 "%s\n"
468 "The above X.509 certificate could not be verified, possibly because you do not have\n"
469 "the CA certificate in your certificate store, or the certificate has expired.\n"
470 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
471 common_name, subject, issuer, fp_str);
472
473 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
474 free(fp_str);
475 free(title);
476 free(message);
477 return rc;
478}
479
480BOOL sdl_cert_dialog_show(const char* title, const char* message)
481{
482 int buttonid = -1;
483 enum
484 {
485 BUTTONID_CERT_ACCEPT_PERMANENT = 23,
486 BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
487 BUTTONID_CERT_DENY = 25
488 };
489 const SDL_MessageBoxButtonData buttons[] = {
490 { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
491 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
492 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
493 };
494
495 const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
496 ARRAYSIZE(buttons), buttons, nullptr };
497 const int rc = SDL_ShowMessageBox(&data, &buttonid);
498
499 Sint32 value = -1;
500 if (rc < 0)
501 value = 0;
502 else
503 {
504 switch (buttonid)
505 {
506 case BUTTONID_CERT_ACCEPT_PERMANENT:
507 value = 1;
508 break;
509 case BUTTONID_CERT_ACCEPT_TEMPORARY:
510 value = 2;
511 break;
512 default:
513 value = 0;
514 break;
515 }
516 }
517
518 return sdl_push_user_event(SDL_EVENT_USER_CERT_RESULT, value);
519}
520
521BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
522{
523 int buttonid = -1;
524 enum
525 {
526 BUTTONID_SHOW_ACCEPT = 24,
527 BUTTONID_SHOW_DENY = 25
528 };
529 const SDL_MessageBoxButtonData buttons[] = {
530 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
531 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
532 };
533
534 const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
535 const SDL_MessageBoxData data = {
536 SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
537 };
538 const int rc = SDL_ShowMessageBox(&data, &buttonid);
539
540 Sint32 value = -1;
541 if (rc < 0)
542 value = 0;
543 else
544 {
545 switch (buttonid)
546 {
547 case BUTTONID_SHOW_ACCEPT:
548 value = 1;
549 break;
550 default:
551 value = 0;
552 break;
553 }
554 }
555
556 return sdl_push_user_event(SDL_EVENT_USER_SHOW_RESULT, value);
557}
558
559BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
560{
561 const std::vector<std::string> auth = { "Username: ", "Domain: ",
562 "Password: " };
563 const std::vector<std::string> authPin = { "Device: ", "PIN: " };
564 const std::vector<std::string> fidoPin = { "FIDO2 PIN: " };
565 const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
566 "GatewayPassword: " };
567 std::vector<std::string> prompt;
568 Sint32 rc = -1;
569
570 switch (args->result)
571 {
572 case AUTH_SMARTCARD_PIN:
573 prompt = authPin;
574 break;
575 case AUTH_FIDO_PIN:
576 prompt = fidoPin;
577 break;
578 case AUTH_RDSTLS:
579 case AUTH_TLS:
580 case AUTH_RDP:
581 case AUTH_NLA:
582 prompt = auth;
583 break;
584 case GW_AUTH_HTTP:
585 case GW_AUTH_RDG:
586 case GW_AUTH_RPC:
587 prompt = gw;
588 break;
589 default:
590 break;
591 }
592
593 std::vector<std::string> result;
594
595 auto parent = SDL_GetMouseFocus();
596 if (!parent)
597 parent = SDL_GetKeyboardFocus();
598
599 if (!prompt.empty())
600 {
601 std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
602 std::vector<Uint32> flags = { SdlInputWidgetPair::SDL_INPUT_READONLY,
603 SdlInputWidgetPair::SDL_INPUT_MASK };
604 if (args->result == AUTH_FIDO_PIN)
605 {
606 initial = { "" };
607 flags = { SdlInputWidgetPair::SDL_INPUT_MASK };
608 }
609 else if (args->result != AUTH_SMARTCARD_PIN)
610 {
611 initial = { args->user ? args->user : "", args->domain ? args->domain : "",
612 args->password ? args->password : "" };
613 flags = { 0, 0, SdlInputWidgetPair::SDL_INPUT_MASK };
614 }
615
616 ssize_t selected = -1;
617 switch (args->result)
618 {
619 case AUTH_SMARTCARD_PIN:
620 case AUTH_RDSTLS:
621 case AUTH_FIDO_PIN:
622 break;
623 default:
624 if (args->user)
625 {
626 selected++;
627 if (args->domain)
628 selected++;
629 }
630 break;
631 }
632 SdlInputWidgetPairList ilist(args->title, prompt, initial, flags, selected);
633 ilist.parent(parent);
634 rc = ilist.run(result);
635 }
636
637 if ((result.size() < prompt.size()))
638 rc = -1;
639
640 char* user = nullptr;
641 char* domain = nullptr;
642 char* pwd = nullptr;
643 if (rc > 0)
644 {
645 if (args->result == AUTH_FIDO_PIN)
646 {
647 pwd = _strdup(result.at(0).c_str());
648 }
649 else
650 {
651 user = _strdup(result.at(0).c_str());
652 if (args->result == AUTH_SMARTCARD_PIN)
653 pwd = _strdup(result.at(1).c_str());
654 else
655 {
656 domain = _strdup(result.at(1).c_str());
657 pwd = _strdup(result.at(2).c_str());
658 }
659 }
660 }
661
662 return sdl_push_user_event(SDL_EVENT_USER_AUTH_RESULT, user, domain, pwd, rc);
663}
664
665BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
666{
667 std::vector<std::string> vlist;
668 vlist.reserve(WINPR_ASSERTING_INT_CAST(size_t, count));
669 for (Sint32 x = 0; x < count; x++)
670 vlist.emplace_back(list[x]);
671 SdlSelectList slist(title, vlist);
672 Sint32 value = slist.run();
673 return sdl_push_user_event(SDL_EVENT_USER_SCARD_RESULT, value);
674}
675
676void sdl_dialogs_uninit()
677{
678 TTF_Quit();
679}
680
681void sdl_dialogs_init()
682{
683 TTF_Init();
684}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
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 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_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.