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