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