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_freerdp.hpp"
30#include "sdl_dialogs.hpp"
31#include "sdl_input.hpp"
32#include "sdl_input_widgets.hpp"
33#include "sdl_select.hpp"
34#include "sdl_selectlist.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 SDLConnectionDialogHider hider(instance);
94
95 const char* target = freerdp_settings_get_server_name(instance->context->settings);
96 switch (reason)
97 {
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 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 std::unique_ptr<char, decltype(&free)> 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 SDLConnectionDialogHider hider(instance);
164 std::vector<std::string> strlist;
165 std::vector<const char*> list;
166 for (DWORD i = 0; i < count; i++)
167 {
168 const SmartcardCertInfo* cert = cert_list[i];
169 char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
170 char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
171
172 char* msg = nullptr;
173 size_t len = 0;
174
175 winpr_asprintf(&msg, &len,
176 "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s",
177 container_name, reader, cert->userHint, cert->domainHint, cert->subject,
178 cert->issuer, cert->upn);
179
180 strlist.emplace_back(msg);
181 free(msg);
182 free(reader);
183 free(container_name);
184
185 auto& m = strlist.back();
186 list.push_back(m.c_str());
187 }
188
189 SDL_Event event = {};
190 const char* title = "Select a logon smartcard certificate";
191 if (gateway)
192 title = "Select a gateway logon smartcard certificate";
193 if (!sdl_push_user_event(SDL_EVENT_USER_SCARD_DIALOG, title, list.data(), count))
194 return res;
195
196 if (!sdl_wait_for_result(instance->context, SDL_EVENT_USER_SCARD_RESULT, &event))
197 return res;
198
199 res = (event.user.code >= 0);
200 *choice = static_cast<DWORD>(event.user.code);
201
202 return res;
203}
204
205SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current,
206 [[maybe_unused]] void* userarg)
207{
208 WINPR_ASSERT(instance);
209 WINPR_ASSERT(instance->context);
210 WINPR_ASSERT(what);
211
212 auto sdl = get_context(instance->context);
213 auto settings = instance->context->settings;
214 const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
215 const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
216
217 sdl->dialog.setTitle("Retry connection to %s",
218 freerdp_settings_get_server_name(instance->context->settings));
219
220 if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
221 {
222 sdl->dialog.showError("Unknown module %s, aborting", what);
223 return -1;
224 }
225
226 if (current == 0)
227 {
228 if (strcmp(what, "arm-transport") == 0)
229 sdl->dialog.showWarn("[%s] Starting your VM. It may take up to 5 minutes", what);
230 }
231
232 if (!enabled)
233 {
234 sdl->dialog.showError(
235 "Automatic reconnection disabled, terminating. Try to connect again later");
236 return -1;
237 }
238
239 const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
240 if (current >= max)
241 {
242 sdl->dialog.showError(
243 "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
244 "tech support for help if this keeps happening.",
245 what);
246 return -1;
247 }
248
249 sdl->dialog.showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz
250 "ms before next attempt",
251 what, current, max, delay);
252 return WINPR_ASSERTING_INT_CAST(ssize_t, delay);
253}
254
255BOOL sdl_present_gateway_message(freerdp* instance, [[maybe_unused]] UINT32 type,
256 BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length,
257 const WCHAR* wmessage)
258{
259 if (!isDisplayMandatory)
260 return TRUE;
261
262 char* title = nullptr;
263 size_t len = 0;
264 winpr_asprintf(&title, &len, "[gateway]");
265
266 Sint32 flags = 0;
267 if (isConsentMandatory)
268 flags = SHOW_DIALOG_ACCEPT_REJECT;
269 else if (isDisplayMandatory)
270 flags = SHOW_DIALOG_TIMED_ACCEPT;
271 char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr);
272
273 SDLConnectionDialogHider hider(instance);
274 const int rc = sdl_show_dialog(instance->context, title, message, flags);
275 free(title);
276 free(message);
277 return rc > 0;
278}
279
280int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
281{
282 int rc = -1;
283 const char* str_data = freerdp_get_logon_error_info_data(data);
284 const char* str_type = freerdp_get_logon_error_info_type(type);
285
286 if (!instance || !instance->context)
287 return -1;
288
289 /* ignore LOGON_MSG_SESSION_CONTINUE message */
290 if (type == LOGON_MSG_SESSION_CONTINUE)
291 return 0;
292
293 SDLConnectionDialogHider hider(instance);
294
295 char* title = nullptr;
296 size_t tlen = 0;
297 winpr_asprintf(&title, &tlen, "[%s] info",
298 freerdp_settings_get_server_name(instance->context->settings));
299
300 char* message = nullptr;
301 size_t mlen = 0;
302 winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type);
303
304 rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT);
305 free(title);
306 free(message);
307 return rc;
308}
309
310static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title,
311 const char* message)
312{
313 SDLConnectionDialogHider hider(context);
314 if (!sdl_push_user_event(SDL_EVENT_USER_CERT_DIALOG, title, message))
315 return 0;
316
317 SDL_Event event = {};
318 if (!sdl_wait_for_result(context, SDL_EVENT_USER_CERT_RESULT, &event))
319 return 0;
320 return static_cast<DWORD>(event.user.code);
321}
322
323static char* sdl_pem_cert(const char* pem)
324{
325 rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
326 if (!cert)
327 return nullptr;
328
329 char* fp = freerdp_certificate_get_fingerprint(cert);
330 char* start = freerdp_certificate_get_validity(cert, TRUE);
331 char* end = freerdp_certificate_get_validity(cert, FALSE);
332 freerdp_certificate_free(cert);
333
334 char* str = nullptr;
335 size_t slen = 0;
336 winpr_asprintf(&str, &slen,
337 "Valid from: %s\n"
338 "Valid to: %s\n"
339 "Thumbprint: %s\n",
340 start, end, fp);
341 free(fp);
342 free(start);
343 free(end);
344 return str;
345}
346
347DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
348 const char* common_name, const char* subject,
349 const char* issuer, const char* new_fingerprint,
350 const char* old_subject, const char* old_issuer,
351 const char* old_fingerprint, DWORD flags)
352{
353 const char* type = type_str_for_flags(flags);
354
355 WINPR_ASSERT(instance);
356 WINPR_ASSERT(instance->context);
357 WINPR_ASSERT(instance->context->settings);
358
359 SDLConnectionDialogHider hider(instance);
360 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
361 * FreeRDP_CertificateCallbackPreferPEM to TRUE
362 */
363 char* new_fp_str = nullptr;
364 size_t len = 0;
365 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
366 new_fp_str = sdl_pem_cert(new_fingerprint);
367 else
368 winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint);
369
370 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
371 * FreeRDP_CertificateCallbackPreferPEM to TRUE
372 */
373 char* old_fp_str = nullptr;
374 size_t olen = 0;
375 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
376 old_fp_str = sdl_pem_cert(old_fingerprint);
377 else
378 winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint);
379
380 const char* collission_str = "";
381 if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
382 {
383 collission_str =
384 "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n"
385 "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n"
386 "The hashing algorithm has been upgraded from SHA1 to SHA256.\n"
387 "All manually accepted certificates must be reconfirmed!\n"
388 "\n";
389 }
390
391 char* title = nullptr;
392 size_t tlen = 0;
393 winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port,
394 type);
395
396 char* message = nullptr;
397 size_t mlen = 0;
398 winpr_asprintf(&message, &mlen,
399 "New Certificate details:\n"
400 "Common Name: %s\n"
401 "Subject: %s\n"
402 "Issuer: %s\n"
403 "%s\n"
404 "Old Certificate details:\n"
405 "Subject: %s\n"
406 "Issuer: %s\n"
407 "%s\n"
408 "%s\n"
409 "The above X.509 certificate does not match the certificate used for previous "
410 "connections.\n"
411 "This may indicate that the certificate has been tampered with.\n"
412 "Please contact the administrator of the RDP server and clarify.\n",
413 common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str,
414 collission_str);
415
416 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
417 free(title);
418 free(message);
419 free(new_fp_str);
420 free(old_fp_str);
421
422 return rc;
423}
424
425DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
426 const char* common_name, const char* subject, const char* issuer,
427 const char* fingerprint, DWORD flags)
428{
429 const char* type = type_str_for_flags(flags);
430
431 /* Newer versions of FreeRDP allow exposing the whole PEM by setting
432 * FreeRDP_CertificateCallbackPreferPEM to TRUE
433 */
434 char* fp_str = nullptr;
435 size_t len = 0;
436 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
437 fp_str = sdl_pem_cert(fingerprint);
438 else
439 winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint);
440
441 char* title = nullptr;
442 size_t tlen = 0;
443 winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type);
444
445 char* message = nullptr;
446 size_t mlen = 0;
447 winpr_asprintf(
448 &message, &mlen,
449 "Common Name: %s\n"
450 "Subject: %s\n"
451 "Issuer: %s\n"
452 "%s\n"
453 "The above X.509 certificate could not be verified, possibly because you do not have\n"
454 "the CA certificate in your certificate store, or the certificate has expired.\n"
455 "Please look at the OpenSSL documentation on how to add a private CA to the store.\n",
456 common_name, subject, issuer, fp_str);
457
458 SDLConnectionDialogHider hider(instance);
459 const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message);
460 free(fp_str);
461 free(title);
462 free(message);
463 return rc;
464}
465
466BOOL sdl_cert_dialog_show(const char* title, const char* message)
467{
468 int buttonid = -1;
469 enum
470 {
471 BUTTONID_CERT_ACCEPT_PERMANENT = 23,
472 BUTTONID_CERT_ACCEPT_TEMPORARY = 24,
473 BUTTONID_CERT_DENY = 25
474 };
475 const SDL_MessageBoxButtonData buttons[] = {
476 { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" },
477 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" },
478 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" }
479 };
480
481 const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message,
482 ARRAYSIZE(buttons), buttons, nullptr };
483 const int rc = SDL_ShowMessageBox(&data, &buttonid);
484
485 Sint32 value = -1;
486 if (rc < 0)
487 value = 0;
488 else
489 {
490 switch (buttonid)
491 {
492 case BUTTONID_CERT_ACCEPT_PERMANENT:
493 value = 1;
494 break;
495 case BUTTONID_CERT_ACCEPT_TEMPORARY:
496 value = 2;
497 break;
498 default:
499 value = 0;
500 break;
501 }
502 }
503
504 return sdl_push_user_event(SDL_EVENT_USER_CERT_RESULT, value);
505}
506
507BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags)
508{
509 int buttonid = -1;
510 enum
511 {
512 BUTTONID_SHOW_ACCEPT = 24,
513 BUTTONID_SHOW_DENY = 25
514 };
515 const SDL_MessageBoxButtonData buttons[] = {
516 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" },
517 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" }
518 };
519
520 const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1;
521 const SDL_MessageBoxData data = {
522 SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr
523 };
524 const int rc = SDL_ShowMessageBox(&data, &buttonid);
525
526 Sint32 value = -1;
527 if (rc < 0)
528 value = 0;
529 else
530 {
531 switch (buttonid)
532 {
533 case BUTTONID_SHOW_ACCEPT:
534 value = 1;
535 break;
536 default:
537 value = 0;
538 break;
539 }
540 }
541
542 return sdl_push_user_event(SDL_EVENT_USER_SHOW_RESULT, value);
543}
544
545BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args)
546{
547 std::vector<std::string> auth = { "Username: ", "Domain: ",
548 "Password: " };
549 std::vector<std::string> authPin = { "Device: ", "PIN: " };
550 std::vector<std::string> gw = { "GatewayUsername: ", "GatewayDomain: ", "GatewayPassword: " };
551 std::vector<std::string> prompt;
552 Sint32 rc = -1;
553
554 switch (args->result)
555 {
556 case AUTH_SMARTCARD_PIN:
557 prompt = std::move(authPin);
558 break;
559 case AUTH_TLS:
560 case AUTH_RDP:
561 case AUTH_NLA:
562 prompt = std::move(auth);
563 break;
564 case GW_AUTH_HTTP:
565 case GW_AUTH_RDG:
566 case GW_AUTH_RPC:
567 prompt = std::move(gw);
568 break;
569 default:
570 break;
571 }
572
573 std::vector<std::string> result;
574
575 if (!prompt.empty())
576 {
577 std::vector<std::string> initial{ args->user ? args->user : "Smartcard", "" };
578 std::vector<Uint32> flags = { SdlInputWidget::SDL_INPUT_READONLY,
579 SdlInputWidget::SDL_INPUT_MASK };
580 if (args->result != AUTH_SMARTCARD_PIN)
581 {
582 initial = { args->user ? args->user : "", args->domain ? args->domain : "",
583 args->password ? args->password : "" };
584 flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK };
585 }
586 SdlInputWidgetList ilist(args->title, prompt, initial, flags);
587 rc = ilist.run(result);
588 }
589
590 if ((result.size() < prompt.size()))
591 rc = -1;
592
593 char* user = nullptr;
594 char* domain = nullptr;
595 char* pwd = nullptr;
596 if (rc > 0)
597 {
598 user = _strdup(result[0].c_str());
599 if (args->result == AUTH_SMARTCARD_PIN)
600 pwd = _strdup(result[1].c_str());
601 else
602 {
603 domain = _strdup(result[1].c_str());
604 pwd = _strdup(result[2].c_str());
605 }
606 }
607 return sdl_push_user_event(SDL_EVENT_USER_AUTH_RESULT, user, domain, pwd, rc);
608}
609
610BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list)
611{
612 std::vector<std::string> vlist;
613 vlist.reserve(WINPR_ASSERTING_INT_CAST(size_t, count));
614 for (Sint32 x = 0; x < count; x++)
615 vlist.emplace_back(list[x]);
616 SdlSelectList slist(title, vlist);
617 Sint32 value = slist.run();
618 return sdl_push_user_event(SDL_EVENT_USER_SCARD_RESULT, value);
619}
620
621void sdl_dialogs_uninit()
622{
623 TTF_Quit();
624}
625
626void sdl_dialogs_init()
627{
628 TTF_Init();
629}
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.