21#include <freerdp/config.h>
26#include <winpr/assert.h>
29#include <winpr/path.h>
30#include <winpr/cmdline.h>
31#include <winpr/winsock.h>
33#include <freerdp/log.h>
34#include <freerdp/version.h>
36#include <winpr/tools/makecert.h>
39#include <sys/select.h>
45#define TAG SERVER_TAG("shadow")
47static const char bind_address[] =
"bind-address,";
49#define fail_at(arg, rc) fail_at_((arg), (rc), __FILE__, __func__, __LINE__)
53 const DWORD level = WLOG_ERROR;
54 wLog* log = WLog_Get(TAG);
55 if (WLog_IsLevelActive(log, level))
56 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt,
57 "Command line parsing failed at '%s' value '%s' [%d]", arg->Name,
62static int shadow_server_print_command_line_help(
int argc,
char** argv,
68 if ((argc < 1) || !largs || !argv)
71 char* path = winpr_GetConfigFilePath(TRUE,
"SAM");
72 printf(
"Usage: %s [options]\n", argv[0]);
74 printf(
"Notes: By default NLA security is active.\n");
75 printf(
"\tIn this mode a SAM database is required.\n");
76 printf(
"\tProvide one with /sam-file:<file with path>\n");
77 printf(
"\telse the default path %s is used.\n", path);
78 printf(
"\tIf there is no existing SAM file authentication for all users will fail.\n");
80 "\n\tIf authentication against PAM is desired, start with -sec-nla (requires compiled in "
81 "support for PAM)\n\n");
83 printf(
" /flag (enables flag)\n");
84 printf(
" /option:<value> (specifies option with value)\n");
85 printf(
" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
93 if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
96 printf(
"%-20s\n", arg->Name);
97 printf(
"\t%s\n", arg->Text);
99 else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
100 (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
106 length = (strlen(arg->Name) + strlen(arg->Format) + 2);
107 str = (
char*)malloc(length + 1);
112 (void)sprintf_s(str, length + 1,
"%s:%s", arg->Name, arg->Format);
113 (void)printf(
"%-20s\n", str);
118 printf(
"%-20s\n", arg->Name);
121 printf(
"\t%s\n", arg->Text);
123 else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
125 length = strlen(arg->Name) + 32;
126 str = (
char*)malloc(length + 1);
131 (void)sprintf_s(str, length + 1,
"%s (default:%s)", arg->Name,
132 arg->Default ?
"on" :
"off");
133 (void)printf(
" %s", arg->Default ?
"-" :
"+");
134 (void)printf(
"%-20s\n", str);
136 (void)printf(
"\t%s\n", arg->Text);
138 }
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
143int shadow_server_command_line_status_print(rdpShadowServer* server,
int argc,
char** argv,
146 WINPR_UNUSED(server);
148 if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
150 printf(
"FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
151 return COMMAND_LINE_STATUS_PRINT_VERSION;
153 else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
155 printf(
"%s\n", freerdp_get_build_config());
156 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
158 else if (status == COMMAND_LINE_STATUS_PRINT)
160 return COMMAND_LINE_STATUS_PRINT;
164 if (shadow_server_print_command_line_help(argc, argv, cargs) < 0)
167 return COMMAND_LINE_STATUS_PRINT_HELP;
173int shadow_server_parse_command_line(rdpShadowServer* server,
int argc,
char** argv,
179 rdpSettings* settings = server->settings;
181 if ((argc < 2) || !argv || !cargs)
184 CommandLineClearArgumentsA(cargs);
185 flags = COMMAND_LINE_SEPARATOR_COLON;
186 flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
187 status = CommandLineParseArgumentsA(argc, argv, cargs, flags, server, NULL, NULL);
197 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
200 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg,
"port")
202 long val = strtol(arg->Value, NULL, 0);
204 if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
205 return fail_at(arg, COMMAND_LINE_ERROR);
207 server->port = (DWORD)val;
209 CommandLineSwitchCase(arg,
"ipc-socket")
212 if (server->ipcSocket)
213 return fail_at(arg, COMMAND_LINE_ERROR);
214 server->ipcSocket = _strdup(arg->Value);
216 if (!server->ipcSocket)
217 return fail_at(arg, COMMAND_LINE_ERROR);
219 CommandLineSwitchCase(arg,
"bind-address")
222 size_t len = strlen(arg->Value) +
sizeof(bind_address);
224 if (server->ipcSocket)
225 return fail_at(arg, COMMAND_LINE_ERROR);
226 server->ipcSocket = calloc(len,
sizeof(CHAR));
228 if (!server->ipcSocket)
229 return fail_at(arg, COMMAND_LINE_ERROR);
231 rc = _snprintf(server->ipcSocket, len,
"%s%s", bind_address, arg->Value);
232 if ((rc < 0) || ((
size_t)rc != len - 1))
233 return fail_at(arg, COMMAND_LINE_ERROR);
235 CommandLineSwitchCase(arg,
"may-view")
237 server->mayView = arg->Value ? TRUE : FALSE;
239 CommandLineSwitchCase(arg,
"bitmap-compat")
241 server->SupportMultiRectBitmapUpdates = arg->Value ? FALSE : TRUE;
243 CommandLineSwitchCase(arg,
"may-interact")
245 server->mayInteract = arg->Value ? TRUE : FALSE;
247 CommandLineSwitchCase(arg,
"max-connections")
250 unsigned long val = strtoul(arg->Value, NULL, 0);
252 if ((errno != 0) || (val > UINT32_MAX))
253 return fail_at(arg, COMMAND_LINE_ERROR);
254 server->maxClientsConnected = val;
256 CommandLineSwitchCase(arg,
"rect")
264 char* str = _strdup(arg->Value);
267 return fail_at(arg, COMMAND_LINE_ERROR);
270 p = strchr(p + 1,
',');
275 return fail_at(arg, COMMAND_LINE_ERROR);
280 p = strchr(p + 1,
',');
285 return fail_at(arg, COMMAND_LINE_ERROR);
290 p = strchr(p + 1,
',');
295 return fail_at(arg, COMMAND_LINE_ERROR);
300 x = strtol(tok[0], NULL, 0);
305 y = strtol(tok[1], NULL, 0);
310 w = strtol(tok[2], NULL, 0);
315 h = strtol(tok[3], NULL, 0);
323 if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
324 return fail_at(arg, COMMAND_LINE_ERROR);
326 if ((x > UINT16_MAX) || (y > UINT16_MAX) || (x + w > UINT16_MAX) ||
327 (y + h > UINT16_MAX))
328 return fail_at(arg, COMMAND_LINE_ERROR);
329 server->subRect.left = (UINT16)x;
330 server->subRect.top = (UINT16)y;
331 server->subRect.right = (UINT16)(x + w);
332 server->subRect.bottom = (UINT16)(y + h);
333 server->shareSubRect = TRUE;
335 CommandLineSwitchCase(arg,
"auth")
337 server->authentication = arg->Value ? TRUE : FALSE;
339 CommandLineSwitchCase(arg,
"remote-guard")
342 arg->Value ? TRUE : FALSE))
343 return fail_at(arg, COMMAND_LINE_ERROR);
345 CommandLineSwitchCase(arg,
"sec")
347 if (strcmp(
"rdp", arg->Value) == 0)
350 return fail_at(arg, COMMAND_LINE_ERROR);
352 return fail_at(arg, COMMAND_LINE_ERROR);
354 return fail_at(arg, COMMAND_LINE_ERROR);
356 return fail_at(arg, COMMAND_LINE_ERROR);
358 return fail_at(arg, COMMAND_LINE_ERROR);
360 else if (strcmp(
"tls", arg->Value) == 0)
363 return fail_at(arg, COMMAND_LINE_ERROR);
365 return fail_at(arg, COMMAND_LINE_ERROR);
367 return fail_at(arg, COMMAND_LINE_ERROR);
369 return fail_at(arg, COMMAND_LINE_ERROR);
371 else if (strcmp(
"nla", arg->Value) == 0)
374 return fail_at(arg, COMMAND_LINE_ERROR);
376 return fail_at(arg, COMMAND_LINE_ERROR);
378 return fail_at(arg, COMMAND_LINE_ERROR);
380 return fail_at(arg, COMMAND_LINE_ERROR);
382 else if (strcmp(
"ext", arg->Value) == 0)
385 return fail_at(arg, COMMAND_LINE_ERROR);
387 return fail_at(arg, COMMAND_LINE_ERROR);
389 return fail_at(arg, COMMAND_LINE_ERROR);
391 return fail_at(arg, COMMAND_LINE_ERROR);
395 WLog_ERR(TAG,
"unknown protocol security: %s", arg->Value);
396 return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE);
399 CommandLineSwitchCase(arg,
"sec-rdp")
402 arg->Value ? TRUE : FALSE))
403 return fail_at(arg, COMMAND_LINE_ERROR);
405 CommandLineSwitchCase(arg,
"sec-tls")
408 arg->Value ? TRUE : FALSE))
409 return fail_at(arg, COMMAND_LINE_ERROR);
411 CommandLineSwitchCase(arg,
"sec-nla")
414 arg->Value ? TRUE : FALSE))
415 return fail_at(arg, COMMAND_LINE_ERROR);
417 CommandLineSwitchCase(arg,
"sec-ext")
420 arg->Value ? TRUE : FALSE))
421 return fail_at(arg, COMMAND_LINE_ERROR);
423 CommandLineSwitchCase(arg,
"sam-file")
426 return fail_at(arg, COMMAND_LINE_ERROR);
428 CommandLineSwitchCase(arg,
"log-level")
430 wLog* root = WLog_GetRoot();
432 if (!WLog_SetStringLogLevel(root, arg->Value))
433 return fail_at(arg, COMMAND_LINE_ERROR);
435 CommandLineSwitchCase(arg,
"log-filters")
437 if (!WLog_AddStringLogFilters(arg->Value))
438 return fail_at(arg, COMMAND_LINE_ERROR);
440 CommandLineSwitchCase(arg,
"nsc")
443 return fail_at(arg, COMMAND_LINE_ERROR);
445 CommandLineSwitchCase(arg,
"rfx")
448 arg->Value ? TRUE : FALSE))
449 return fail_at(arg, COMMAND_LINE_ERROR);
451 CommandLineSwitchCase(arg,
"gfx")
454 arg->Value ? TRUE : FALSE))
455 return fail_at(arg, COMMAND_LINE_ERROR);
457 CommandLineSwitchCase(arg,
"gfx-progressive")
460 arg->Value ? TRUE : FALSE))
461 return fail_at(arg, COMMAND_LINE_ERROR);
463 CommandLineSwitchCase(arg,
"gfx-rfx")
466 arg->Value ? TRUE : FALSE))
467 return fail_at(arg, COMMAND_LINE_ERROR);
469 CommandLineSwitchCase(arg,
"gfx-planar")
472 return fail_at(arg, COMMAND_LINE_ERROR);
474 CommandLineSwitchCase(arg,
"gfx-avc420")
477 return fail_at(arg, COMMAND_LINE_ERROR);
479 CommandLineSwitchCase(arg,
"gfx-avc444")
482 arg->Value ? TRUE : FALSE))
483 return fail_at(arg, COMMAND_LINE_ERROR);
485 return fail_at(arg, COMMAND_LINE_ERROR);
487 CommandLineSwitchCase(arg,
"keytab")
490 return fail_at(arg, COMMAND_LINE_ERROR);
492 CommandLineSwitchCase(arg,
"ccache")
495 return fail_at(arg, COMMAND_LINE_ERROR);
497 CommandLineSwitchCase(arg,
"tls-secrets-file")
500 return fail_at(arg, COMMAND_LINE_ERROR);
502 CommandLineSwitchDefault(arg)
505 CommandLineSwitchEnd(arg)
506 }
while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
508 arg = CommandLineFindArgumentA(cargs,
"monitors");
510 if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
512 UINT32 numMonitors = 0;
514 numMonitors = shadow_enum_monitors(monitors, 16);
516 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
519 long val = strtol(arg->Value, NULL, 0);
521 if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
522 status = COMMAND_LINE_STATUS_PRINT;
524 server->selectedMonitor = (UINT32)val;
530 for (UINT32 index = 0; index < numMonitors; index++)
533 const INT64 width = monitor->right - monitor->left + 1;
534 const INT64 height = monitor->bottom - monitor->top + 1;
535 WLog_INFO(TAG,
" %s [%d] %" PRId64
"x%" PRId64
"\t+%" PRId32
"+%" PRId32
"",
536 (monitor->flags == 1) ?
"*" :
" ", index, width, height, monitor->left,
540 status = COMMAND_LINE_STATUS_PRINT;
547 if (!server->authentication)
550 return COMMAND_LINE_ERROR;
555static DWORD WINAPI shadow_server_thread(LPVOID arg)
557 rdpShadowServer* server = (rdpShadowServer*)arg;
560 freerdp_listener* listener = server->listener;
561 shadow_subsystem_start(server->subsystem);
565 HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
567 events[nCount++] = server->StopEvent;
568 nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
572 WLog_ERR(TAG,
"Failed to get FreeRDP file descriptor");
576 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
587 if (!listener->CheckFileDescriptor(listener))
589 WLog_ERR(TAG,
"Failed to check FreeRDP file descriptor");
603 listener->Close(listener);
604 shadow_subsystem_stop(server->subsystem);
608 if (shadow_client_boardcast_quit(server, 0))
610 while (ArrayList_Count(server->clients) > 0)
620static BOOL open_port(rdpShadowServer* server,
char* address)
623 char* modaddr = address;
627 if (modaddr[0] ==
'[')
629 char* end = strchr(address,
']');
632 WLog_ERR(TAG,
"Could not parse bind-address %s", address);
638 WLog_ERR(TAG,
"Excess data after IPv6 address: '%s'", end);
644 status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
649 "Problem creating TCP listener. (Port already used or insufficient permissions?)");
655int shadow_server_start(rdpShadowServer* server)
664 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
668 (void)signal(SIGPIPE, SIG_IGN);
670 server->screen = shadow_screen_new(server);
674 WLog_ERR(TAG,
"screen_new failed");
678 server->capture = shadow_capture_new(server);
680 if (!server->capture)
682 WLog_ERR(TAG,
"capture_new failed");
692 ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
693 strnlen(bind_address,
sizeof(bind_address))) != 0);
698 char** ptr = CommandLineParseCommaSeparatedValuesEx(NULL, server->ipcSocket, &count);
699 if (!ptr || (count <= 1))
701 if (server->ipcSocket == NULL)
703 if (!open_port(server, NULL))
705 CommandLineParserFree(ptr);
711 CommandLineParserFree(ptr);
716 WINPR_ASSERT(ptr || (count == 0));
717 for (
size_t x = 1; x < count; x++)
719 BOOL success = open_port(server, ptr[x]);
722 CommandLineParserFree(ptr);
726 CommandLineParserFree(ptr);
730 status = server->listener->OpenLocal(server->listener, server->ipcSocket);
734 WLog_ERR(TAG,
"Problem creating local socket listener. (Port already used or "
735 "insufficient permissions?)");
740 if (!(server->thread = CreateThread(NULL, 0, shadow_server_thread, (
void*)server, 0, NULL)))
748int shadow_server_stop(rdpShadowServer* server)
755 (void)SetEvent(server->StopEvent);
756 (void)WaitForSingleObject(server->thread, INFINITE);
757 (void)CloseHandle(server->thread);
758 server->thread = NULL;
759 if (server->listener && server->listener->Close)
760 server->listener->Close(server->listener);
765 shadow_screen_free(server->screen);
766 server->screen = NULL;
771 shadow_capture_free(server->capture);
772 server->capture = NULL;
778static int shadow_server_init_config_path(rdpShadowServer* server)
780 if (!server->ConfigPath)
786 if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, 0))
788 WLog_ERR(TAG,
"Failed to create directory '%s'", configHome);
793 server->ConfigPath = configHome;
797 if (!server->ConfigPath)
803static BOOL shadow_server_create_certificate(rdpShadowServer* server,
const char* filepath)
806 char* makecert_argv[6] = {
"makecert",
"-rdp",
"-live",
"-silent",
"-y",
"5" };
808 WINPR_STATIC_ASSERT(ARRAYSIZE(makecert_argv) <= INT_MAX);
809 const size_t makecert_argc = ARRAYSIZE(makecert_argv);
811 MAKECERT_CONTEXT* makecert = makecert_context_new();
816 if (makecert_context_process(makecert, (
int)makecert_argc, makecert_argv) < 0)
819 if (makecert_context_set_output_file_name(makecert,
"shadow") != 1)
822 WINPR_ASSERT(server);
823 WINPR_ASSERT(filepath);
824 if (!winpr_PathFileExists(server->CertificateFile))
826 if (makecert_context_output_certificate_file(makecert, filepath) != 1)
830 if (!winpr_PathFileExists(server->PrivateKeyFile))
832 if (makecert_context_output_private_key_file(makecert, filepath) != 1)
837 makecert_context_free(makecert);
840static BOOL shadow_server_init_certificate(rdpShadowServer* server)
842 char* filepath = NULL;
845 WINPR_ASSERT(server);
847 if (!winpr_PathFileExists(server->ConfigPath) && !winpr_PathMakePath(server->ConfigPath, 0))
849 WLog_ERR(TAG,
"Failed to create directory '%s'", server->ConfigPath);
853 if (!(filepath = GetCombinedPath(server->ConfigPath,
"shadow")))
856 if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, 0))
858 if (!CreateDirectoryA(filepath, 0))
860 WLog_ERR(TAG,
"Failed to create directory '%s'", filepath);
865 server->CertificateFile = GetCombinedPath(filepath,
"shadow.crt");
866 server->PrivateKeyFile = GetCombinedPath(filepath,
"shadow.key");
868 if (!server->CertificateFile || !server->PrivateKeyFile)
871 if ((!winpr_PathFileExists(server->CertificateFile)) ||
872 (!winpr_PathFileExists(server->PrivateKeyFile)))
874 if (!shadow_server_create_certificate(server, filepath))
878 rdpSettings* settings = server->settings;
879 WINPR_ASSERT(settings);
881 rdpPrivateKey* key = freerdp_key_new_from_file(server->PrivateKeyFile);
887 rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
894 if (!freerdp_certificate_is_rdp_security_compatible(cert))
907static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
909 WINPR_ASSERT(listener);
911 rdpShadowServer* server = (rdpShadowServer*)listener->info;
912 WINPR_ASSERT(server);
914 if (server->maxClientsConnected > 0)
916 const size_t count = ArrayList_Count(server->clients);
917 if (count >= server->maxClientsConnected)
919 WLog_WARN(TAG,
"connection limit [%" PRIuz
"] reached, discarding client",
920 server->maxClientsConnected);
927int shadow_server_init(rdpShadowServer* server)
930 winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
931 WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
933 if (!(server->clients = ArrayList_New(TRUE)))
936 if (!(server->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
939 if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
942 status = shadow_server_init_config_path(server);
947 if (!shadow_server_init_certificate(server))
950 server->listener = freerdp_listener_new();
952 if (!server->listener)
955 server->listener->info = (
void*)server;
956 server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
957 server->listener->PeerAccepted = shadow_client_accepted;
958 server->subsystem = shadow_subsystem_new();
960 if (!server->subsystem)
963 status = shadow_subsystem_init(server->subsystem, server);
970 shadow_server_uninit(server);
971 WLog_ERR(TAG,
"Failed to initialize shadow server");
975int shadow_server_uninit(rdpShadowServer* server)
980 shadow_server_stop(server);
981 shadow_subsystem_uninit(server->subsystem);
982 shadow_subsystem_free(server->subsystem);
983 server->subsystem = NULL;
984 freerdp_listener_free(server->listener);
985 server->listener = NULL;
986 free(server->CertificateFile);
987 server->CertificateFile = NULL;
988 free(server->PrivateKeyFile);
989 server->PrivateKeyFile = NULL;
990 free(server->ConfigPath);
991 server->ConfigPath = NULL;
992 DeleteCriticalSection(&(server->lock));
993 (void)CloseHandle(server->StopEvent);
994 server->StopEvent = NULL;
995 ArrayList_Free(server->clients);
996 server->clients = NULL;
1000rdpShadowServer* shadow_server_new(
void)
1002 rdpShadowServer* server = NULL;
1003 server = (rdpShadowServer*)calloc(1,
sizeof(rdpShadowServer));
1008 server->SupportMultiRectBitmapUpdates = TRUE;
1009 server->port = 3389;
1010 server->mayView = TRUE;
1011 server->mayInteract = TRUE;
1012 server->h264RateControlMode = H264_RATECONTROL_VBR;
1013 server->h264BitRate = 10000000;
1014 server->h264FrameRate = 30;
1016 server->authentication = TRUE;
1021void shadow_server_free(rdpShadowServer* server)
1026 free(server->ipcSocket);
1027 server->ipcSocket = NULL;
1029 server->settings = NULL;
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *param)
Sets a string settings value. The param is copied.
FREERDP_API rdpSettings * freerdp_settings_new(DWORD flags)
creates a new setting struct
FREERDP_API BOOL freerdp_settings_set_pointer_len(rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id, const void *data, size_t len)
Set a pointer to value data.
FREERDP_API void freerdp_settings_free(rdpSettings *settings)
Free a settings struct with all data in it.
#define FREERDP_SETTINGS_SERVER_MODE
FREERDP_API char * freerdp_settings_get_config_path(void)
return the configuration directory for the library
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL param)
Sets a BOOL settings value.