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