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