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