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