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 int rc = -1;
307 const char* str_data = freerdp_get_logon_error_info_data(data);
308 const char* str_type = freerdp_get_logon_error_info_type(type);
309
310 if (!instance || !instance->context)
311 return -1;
312
313 /* ignore LOGON_MSG_SESSION_CONTINUE message */
314 if (type == LOGON_MSG_SESSION_CONTINUE)
315 return 0;
316
317 char* title = nullptr;
318 size_t tlen = 0;
319 winpr_asprintf(&title, &tlen, "[%s] info",
320 freerdp_settings_get_server_name(instance->context->settings));
321
322 char* message = nullptr;
323 size_t mlen = 0;
324 winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
325
326 rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
327 free(title);
328 free(message);
329 return rc;
330}
331
332static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
333 const char* message)
334{
335 if (!sdl_push_user_event(SDL_EVENT_USER_CERT_DIALOG, title, message))
336 return 0;
337
338 SDL_Event event = {};
339 if (!sdl_wait_for_result(context, SDL_EVENT_USER_CERT_RESULT, &event))
340 return 0;
341 return static_cast<DWORD>(event.user.code);
342}
343
344static char* sdl_pem_cert(const char* pem)
345{
346 rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
347 if (!cert)
348 return nullptr;
349
350 char* fp = freerdp_certificate_get_fingerprint(cert);
351 char* start = freerdp_certificate_get_validity(cert, TRUE);
352 char* end = freerdp_certificate_get_validity(cert, FALSE);
353 freerdp_certificate_free(cert);
354
355 char* str = nullptr;
356 size_t slen = 0;
357 winpr_asprintf(&str, &slen,
358 "Valid from: %s\n"
359 "Valid to: %s\n"
360 "Thumbprint: %s\n",
361 start, end, fp);
362 free(fp);
363 free(start);
364 free(end);
365 return str;
366}
367
368DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
369 const char* common_name, const char* subject,
370 const char* issuer, const char* new_fingerprint,
371 const char* old_subject, const char* old_issuer,
372 const char* old_fingerprint, DWORD flags)
373{
374 const char* type = type_str_for_flags(flags);
375
376 WINPR_ASSERT(instance);
377 WINPR_ASSERT(instance->context);
378 WINPR_ASSERT(instance->context->settings);
379
380 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
381 * FreeRDP_CertificateCallbackPreferPEM to TRUE
382 */
383 char* new_fp_str = nullptr;
384 size_t len = 0;
385 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
386 new_fp_str = sdl_pem_cert(new_fingerprint);
387 else
388 winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
389
390 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
391 * FreeRDP_CertificateCallbackPreferPEM to TRUE
392 */
393 char* old_fp_str = nullptr;
394 size_t olen = 0;
395 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
396 old_fp_str = sdl_pem_cert(old_fingerprint);
397 else
398 winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
399
400 const char* collission_str = "";
401 if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
402 {
403 collission_str =
404 "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
405 "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
406 "The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
407 "All manually accepted certificates must be reconfirmed!\n"
408 "\n";
409 }
410
411 char* title = nullptr;
412 size_t tlen = 0;
413 winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
414 type);
415
416 char* message = nullptr;
417 size_t mlen = 0;
418 winpr_asprintf(&message, &mlen,
419 "New Certificate details:\n"
420 "Common Name: %s\n"
421 "Subject: %s\n"
422 "Issuer: %s\n"
423 "%s\n"
424 "Old Certificate details:\n"
425 "Subject: %s\n"
426 "Issuer: %s\n"
427 "%s\n"
428 "%s\n"
429 "The above X.509 certificate does not match the certificate used for previous "
430 "connections.\n"
431 "This may indicate that the certificate has been tampered with.\n"
432 "Please contact the administrator of the RDP server and clarify.\n",
433 common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
434 collission_str);
435
436 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
437 free(title);
438 free(message);
439 free(new_fp_str);
440 free(old_fp_str);
441
442 return rc;
443}
444
445DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
446 const char* common_name, const char* subject, const char* issuer,
447 const char* fingerprint, DWORD flags)
448{
449 const char* type = type_str_for_flags(flags);
450
451 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
452 * FreeRDP_CertificateCallbackPreferPEM to TRUE
453 */
454 char* fp_str = nullptr;
455 size_t len = 0;
456 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
457 fp_str = sdl_pem_cert(fingerprint);
458 else
459 winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
460
461 char* title = nullptr;
462 size_t tlen = 0;
463 winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
464
465 char* message = nullptr;
466 size_t mlen = 0;
467 winpr_asprintf(
468 &message, &mlen,
469 "Common Name: %s\n"
470 "Subject: %s\n"
471 "Issuer: %s\n"
472 "%s\n"
473 "The above X.509 certificate could not be verified, possibly because you do not have\n"
474 "the CA certificate in your certificate store, or the certificate has expired.\n"
475 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
476 common_name, subject, issuer, fp_str);
477
478 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
479 free(fp_str);
480 free(title);
481 free(message);
482 return rc;
483}
484
485BOOL sdl_cert_dialog_show(const char* title, const char* message)
486{
487 int buttonid = -1;
488 enum
489 {
490 BUTTONID_CERT_ACCEPT_PERMANENT = 23,
491 BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
492 BUTTONID_CERT_DENY = 25
493 };
494 const SDL_MessageBoxButtonData buttons[] = {
495 { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
496 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
497 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
498 };
499
500 const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
501 ARRAYSIZE(buttons), buttons, nullptr };
502 const int rc = SDL_ShowMessageBox(&data, &buttonid);
503
504 Sint32 value = -1;
505 if (rc < 0)
506 value = 0;
507 else
508 {
509 switch (buttonid)
510 {
511 case BUTTONID_CERT_ACCEPT_PERMANENT:
512 value = 1;
513 break;
514 case BUTTONID_CERT_ACCEPT_TEMPORARY:
515 value = 2;
516 break;
517 default:
518 value = 0;
519 break;
520 }
521 }
522
523 return sdl_push_user_event(SDL_EVENT_USER_CERT_RESULT, value);
524}
525
526BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
527{
528 int buttonid = -1;
529 enum
530 {
531 BUTTONID_SHOW_ACCEPT = 24,
532 BUTTONID_SHOW_DENY = 25
533 };
534 const SDL_MessageBoxButtonData buttons[] = {
535 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
536 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
537 };
538
539 const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
540 const SDL_MessageBoxData data = {
541 SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
542 };
543 const int rc = SDL_ShowMessageBox(&data, &buttonid);
544
545 Sint32 value = -1;
546 if (rc < 0)
547 value = 0;
548 else
549 {
550 switch (buttonid)
551 {
552 case BUTTONID_SHOW_ACCEPT:
553 value = 1;
554 break;
555 default:
556 value = 0;
557 break;
558 }
559 }
560
561 return sdl_push_user_event(SDL_EVENT_USER_SHOW_RESULT, value);
562}
563
564BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
565{
566 const std::vector<std::string> auth = { "Username: ", "Domain: ",
567 "Password: " };
568 const std::vector<std::string> authPin = { "Device: ", "PIN: " };
569 const std::vector<std::string> fidoPin = { "FIDO2 PIN: " };
570 const std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ",
571 "GatewayPassword: " };
572 std::vector<std::string> prompt;
573 Sint32 rc = -1;
574
575 switch (args->result)
576 {
577 case AUTH_SMARTCARD_PIN:
578 prompt = authPin;
579 break;
580 case AUTH_FIDO_PIN:
581 prompt = fidoPin;
582 break;
583 case AUTH_RDSTLS:
584 case AUTH_TLS:
585 case AUTH_RDP:
586 case AUTH_NLA:
587 prompt = auth;
588 break;
589 case GW_AUTH_HTTP:
590 case GW_AUTH_RDG:
591 case GW_AUTH_RPC:
592 prompt = gw;
593 break;
594 default:
595 break;
596 }
597
598 std::vector<std::string> result;
599
600 auto parent = SDL_GetMouseFocus();
601 if (!parent)
602 parent = SDL_GetKeyboardFocus();
603
604 if (!prompt.empty())
605 {
606 std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
607 std::vector<Uint32> flags = { SdlInputWidgetPair::SDL_INPUT_READONLY,
608 SdlInputWidgetPair::SDL_INPUT_MASK };
609 if (args->result == AUTH_FIDO_PIN)
610 {
611 initial = { "" };
612 flags = { SdlInputWidgetPair::SDL_INPUT_MASK };
613 }
614 else if (args->result != AUTH_SMARTCARD_PIN)
615 {
616 initial = { args->user ? args->user : "", args->domain ? args->domain : "",
617 args->password ? args->password : "" };
618 flags = { 0, 0, SdlInputWidgetPair::SDL_INPUT_MASK };
619 }
620
621 ssize_t selected = -1;
622 switch (args->result)
623 {
624 case AUTH_SMARTCARD_PIN:
625 case AUTH_RDSTLS:
626 case AUTH_FIDO_PIN:
627 break;
628 default:
629 if (args->user)
630 {
631 selected++;
632 if (args->domain)
633 selected++;
634 }
635 break;
636 }
637 SdlInputWidgetPairList ilist(args->title, prompt, initial, flags, selected);
638 ilist.parent(parent);
639 rc = ilist.run(result);
640 }
641
642 if ((result.size() < prompt.size()))
643 rc = -1;
644
645 char* user = nullptr;
646 char* domain = nullptr;
647 char* pwd = nullptr;
648 if (rc > 0)
649 {
650 if (args->result == AUTH_FIDO_PIN)
651 {
652 pwd = _strdup(result.at(0).c_str());
653 }
654 else
655 {
656 user = _strdup(result.at(0).c_str());
657 if (args->result == AUTH_SMARTCARD_PIN)
658 pwd = _strdup(result.at(1).c_str());
659 else
660 {
661 domain = _strdup(result.at(1).c_str());
662 pwd = _strdup(result.at(2).c_str());
663 }
664 }
665 }
666
667 return sdl_push_user_event(SDL_EVENT_USER_AUTH_RESULT, user, domain, pwd, rc);
668}
669
670BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
671{
672 std::vector<std::string> vlist;
673 vlist.reserve(WINPR_ASSERTING_INT_CAST(size_t, count));
674 for (Sint32 x = 0; x < count; x++)
675 vlist.emplace_back(list[x]);
676 SdlSelectList slist(title, vlist);
677 Sint32 value = slist.run();
678 return sdl_push_user_event(SDL_EVENT_USER_SCARD_RESULT, value);
679}
680
681void sdl_dialogs_uninit()
682{
683 TTF_Quit();
684}
685
686void sdl_dialogs_init()
687{
688 TTF_Init();
689}
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.