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_PrintTextMessage(log, level, line, file, fkt,
57 "Command line parsing failed at '%s' value '%s' [%d]", arg->Name,
63static int command_line_compare(
const void* pa,
const void* pb)
75 return strcmp(a->Name, b->Name);
79static int shadow_server_print_command_line_help(
int argc,
char** argv,
82 if ((argc < 1) || !largs || !argv)
86 char* path = winpr_GetConfigFilePath(TRUE,
"SAM");
87 printf(
"Usage: %s [options]\n", argv[0]);
89 printf(
"Notes: By default NLA security is active.\n");
90 printf(
"\tIn this mode a SAM database is required.\n");
91 printf(
"\tProvide one with /sam-file:<file with path>\n");
92 printf(
"\telse the default path %s is used.\n", path);
93 printf(
"\tIf there is no existing SAM file authentication for all users will fail.\n");
94 printf(
"\n\tIf authentication against PAM is desired, start with -sec-nla (requires "
96 "support for PAM)\n\n");
98 printf(
" /flag (enables flag)\n");
99 printf(
" /option:<value> (specifies option with value)\n");
100 printf(
" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
109 while (arg->Name !=
nullptr)
127 if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
130 printf(
"%-20s\n", arg->Name);
131 printf(
"\t%s\n", arg->Text);
133 else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
134 (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
140 const size_t length = (strlen(arg->Name) + strlen(arg->Format) + 2);
141 char* str = (
char*)calloc(length + 1,
sizeof(
char));
146 (void)sprintf_s(str, length + 1,
"%s:%s", arg->Name, arg->Format);
147 (void)printf(
"%-20s\n", str);
152 printf(
"%-20s\n", arg->Name);
155 printf(
"\t%s\n", arg->Text);
157 else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
159 const size_t length = strlen(arg->Name) + 32;
160 char* str = calloc(length + 1,
sizeof(
char));
165 (void)sprintf_s(str, length + 1,
"%s (default:%s)", arg->Name,
166 arg->Default ?
"on" :
"off");
167 (void)printf(
" %s", arg->Default ?
"-" :
"+");
168 (void)printf(
"%-20s\n", str);
169 (void)printf(
"\t%s\n", arg->Text);
173 }
while ((arg = CommandLineFindNextArgumentA(arg)) !=
nullptr);
181int shadow_server_command_line_status_print(rdpShadowServer* server,
int argc,
char** argv,
184 WINPR_UNUSED(server);
186 if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
188 printf(
"FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
189 return COMMAND_LINE_STATUS_PRINT_VERSION;
191 else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
193 printf(
"%s\n", freerdp_get_build_config());
194 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
196 else if (status == COMMAND_LINE_STATUS_PRINT)
198 return COMMAND_LINE_STATUS_PRINT;
202 if (shadow_server_print_command_line_help(argc, argv, cargs) < 0)
205 return COMMAND_LINE_STATUS_PRINT_HELP;
211int shadow_server_parse_command_line(rdpShadowServer* server,
int argc,
char** argv,
217 rdpSettings* settings = server->settings;
219 if ((argc < 2) || !argv || !cargs)
222 CommandLineClearArgumentsA(cargs);
223 flags = COMMAND_LINE_SEPARATOR_COLON;
224 flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
225 status = CommandLineParseArgumentsA(argc, argv, cargs, flags, server,
nullptr,
nullptr);
235 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
238 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg,
"port")
240 long val = strtol(arg->Value,
nullptr, 0);
242 if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
243 return fail_at(arg, COMMAND_LINE_ERROR);
245 server->port = (DWORD)val;
247 CommandLineSwitchCase(arg,
"ipc-socket")
250 if (server->ipcSocket)
251 return fail_at(arg, COMMAND_LINE_ERROR);
252 server->ipcSocket = _strdup(arg->Value);
254 if (!server->ipcSocket)
255 return fail_at(arg, COMMAND_LINE_ERROR);
257 CommandLineSwitchCase(arg,
"bind-address")
260 size_t len = strlen(arg->Value) +
sizeof(bind_address);
262 if (server->ipcSocket)
263 return fail_at(arg, COMMAND_LINE_ERROR);
264 server->ipcSocket = calloc(len,
sizeof(CHAR));
266 if (!server->ipcSocket)
267 return fail_at(arg, COMMAND_LINE_ERROR);
269 rc = _snprintf(server->ipcSocket, len,
"%s%s", bind_address, arg->Value);
270 if ((rc < 0) || ((
size_t)rc != len - 1))
271 return fail_at(arg, COMMAND_LINE_ERROR);
273 CommandLineSwitchCase(arg,
"may-view")
275 server->mayView = arg->Value !=
nullptr;
277 CommandLineSwitchCase(arg,
"bitmap-compat")
279 server->SupportMultiRectBitmapUpdates = arg->Value ==
nullptr;
281 CommandLineSwitchCase(arg,
"may-interact")
283 server->mayInteract = arg->Value !=
nullptr;
285 CommandLineSwitchCase(arg,
"server-side-cursor")
287 server->ShowMouseCursor = arg->Value !=
nullptr;
289 CommandLineSwitchCase(arg,
"mouse-relative")
291 const BOOL val = arg->Value !=
nullptr;
294 return fail_at(arg, COMMAND_LINE_ERROR);
296 CommandLineSwitchCase(arg,
"max-connections")
299 unsigned long val = strtoul(arg->Value,
nullptr, 0);
301 if ((errno != 0) || (val > UINT32_MAX))
302 return fail_at(arg, COMMAND_LINE_ERROR);
303 server->maxClientsConnected = val;
305 CommandLineSwitchCase(arg,
"rect")
313 char* str = _strdup(arg->Value);
316 return fail_at(arg, COMMAND_LINE_ERROR);
319 p = strchr(p + 1,
',');
324 return fail_at(arg, COMMAND_LINE_ERROR);
329 p = strchr(p + 1,
',');
334 return fail_at(arg, COMMAND_LINE_ERROR);
339 p = strchr(p + 1,
',');
344 return fail_at(arg, COMMAND_LINE_ERROR);
349 x = strtol(tok[0],
nullptr, 0);
354 y = strtol(tok[1],
nullptr, 0);
359 w = strtol(tok[2],
nullptr, 0);
364 h = strtol(tok[3],
nullptr, 0);
372 if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
373 return fail_at(arg, COMMAND_LINE_ERROR);
375 if ((x > UINT16_MAX) || (y > UINT16_MAX) || (x + w > UINT16_MAX) ||
376 (y + h > UINT16_MAX))
377 return fail_at(arg, COMMAND_LINE_ERROR);
378 server->subRect.left = (UINT16)x;
379 server->subRect.top = (UINT16)y;
380 server->subRect.right = (UINT16)(x + w);
381 server->subRect.bottom = (UINT16)(y + h);
382 server->shareSubRect = TRUE;
384 CommandLineSwitchCase(arg,
"auth")
386 server->authentication = arg->Value !=
nullptr;
388 CommandLineSwitchCase(arg,
"remote-guard")
391 arg->Value !=
nullptr))
392 return fail_at(arg, COMMAND_LINE_ERROR);
394 CommandLineSwitchCase(arg,
"restricted-admin")
397 arg->Value !=
nullptr))
398 return fail_at(arg, COMMAND_LINE_ERROR);
400 CommandLineSwitchCase(arg,
"vmconnect")
403 return fail_at(arg, COMMAND_LINE_ERROR);
405 CommandLineSwitchCase(arg,
"sec")
407 if (strcmp(
"rdp", arg->Value) == 0)
410 return fail_at(arg, COMMAND_LINE_ERROR);
412 return fail_at(arg, COMMAND_LINE_ERROR);
414 return fail_at(arg, COMMAND_LINE_ERROR);
416 return fail_at(arg, COMMAND_LINE_ERROR);
418 return fail_at(arg, COMMAND_LINE_ERROR);
420 else if (strcmp(
"tls", arg->Value) == 0)
423 return fail_at(arg, COMMAND_LINE_ERROR);
425 return fail_at(arg, COMMAND_LINE_ERROR);
427 return fail_at(arg, COMMAND_LINE_ERROR);
429 return fail_at(arg, COMMAND_LINE_ERROR);
431 else if (strcmp(
"nla", arg->Value) == 0)
434 return fail_at(arg, COMMAND_LINE_ERROR);
436 return fail_at(arg, COMMAND_LINE_ERROR);
438 return fail_at(arg, COMMAND_LINE_ERROR);
440 return fail_at(arg, COMMAND_LINE_ERROR);
442 else if (strcmp(
"ext", arg->Value) == 0)
445 return fail_at(arg, COMMAND_LINE_ERROR);
447 return fail_at(arg, COMMAND_LINE_ERROR);
449 return fail_at(arg, COMMAND_LINE_ERROR);
451 return fail_at(arg, COMMAND_LINE_ERROR);
455 WLog_ERR(TAG,
"unknown protocol security: %s", arg->Value);
456 return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE);
459 CommandLineSwitchCase(arg,
"sec-rdp")
462 return fail_at(arg, COMMAND_LINE_ERROR);
464 CommandLineSwitchCase(arg,
"sec-tls")
467 return fail_at(arg, COMMAND_LINE_ERROR);
469 CommandLineSwitchCase(arg,
"sec-nla")
472 return fail_at(arg, COMMAND_LINE_ERROR);
474 CommandLineSwitchCase(arg,
"sec-ext")
477 return fail_at(arg, COMMAND_LINE_ERROR);
479 CommandLineSwitchCase(arg,
"sam-file")
482 return fail_at(arg, COMMAND_LINE_ERROR);
484 CommandLineSwitchCase(arg,
"log-level")
486 wLog* root = WLog_GetRoot();
488 if (!WLog_SetStringLogLevel(root, arg->Value))
489 return fail_at(arg, COMMAND_LINE_ERROR);
491 CommandLineSwitchCase(arg,
"log-filters")
493 if (!WLog_AddStringLogFilters(arg->Value))
494 return fail_at(arg, COMMAND_LINE_ERROR);
496 CommandLineSwitchCase(arg,
"nsc")
499 return fail_at(arg, COMMAND_LINE_ERROR);
501 CommandLineSwitchCase(arg,
"rfx")
504 return fail_at(arg, COMMAND_LINE_ERROR);
506 CommandLineSwitchCase(arg,
"gfx")
509 arg->Value !=
nullptr))
510 return fail_at(arg, COMMAND_LINE_ERROR);
512 CommandLineSwitchCase(arg,
"gfx-progressive")
515 return fail_at(arg, COMMAND_LINE_ERROR);
517 CommandLineSwitchCase(arg,
"gfx-rfx")
520 return fail_at(arg, COMMAND_LINE_ERROR);
522 CommandLineSwitchCase(arg,
"gfx-planar")
525 return fail_at(arg, COMMAND_LINE_ERROR);
527 CommandLineSwitchCase(arg,
"gfx-avc420")
530 return fail_at(arg, COMMAND_LINE_ERROR);
532 CommandLineSwitchCase(arg,
"gfx-avc444")
535 return fail_at(arg, COMMAND_LINE_ERROR);
537 return fail_at(arg, COMMAND_LINE_ERROR);
539 CommandLineSwitchCase(arg,
"keytab")
542 return fail_at(arg, COMMAND_LINE_ERROR);
544 CommandLineSwitchCase(arg,
"ccache")
547 return fail_at(arg, COMMAND_LINE_ERROR);
549 CommandLineSwitchCase(arg,
"tls-secrets-file")
552 return fail_at(arg, COMMAND_LINE_ERROR);
554 CommandLineSwitchDefault(arg)
557 CommandLineSwitchEnd(arg)
558 }
while ((arg = CommandLineFindNextArgumentA(arg)) !=
nullptr);
560 arg = CommandLineFindArgumentA(cargs,
"monitors");
562 if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
564 UINT32 numMonitors = 0;
566 numMonitors = shadow_enum_monitors(monitors, 16);
568 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
571 long val = strtol(arg->Value,
nullptr, 0);
573 if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
574 status = COMMAND_LINE_STATUS_PRINT;
576 server->selectedMonitor = (UINT32)val;
582 for (UINT32 index = 0; index < numMonitors; index++)
585 const INT64 width = monitor->right - monitor->left + 1;
586 const INT64 height = monitor->bottom - monitor->top + 1;
588 TAG,
" %s [%" PRIu32
"] %" PRId64
"x%" PRId64
"\t+%" PRId32
"+%" PRId32
"",
589 (monitor->flags == 1) ?
"*" :
" ", index, width, height, monitor->left,
593 status = COMMAND_LINE_STATUS_PRINT;
603 return COMMAND_LINE_ERROR;
609static DWORD WINAPI shadow_server_thread(LPVOID arg)
611 rdpShadowServer* server = (rdpShadowServer*)arg;
614 freerdp_listener* listener = server->listener;
615 if (shadow_subsystem_start(server->subsystem) < 0)
620 HANDLE events[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT;
622 events[nCount++] = server->StopEvent;
623 nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
627 WLog_ERR(TAG,
"Failed to get FreeRDP file descriptor");
631 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
642 if (!listener->CheckFileDescriptor(listener))
644 WLog_ERR(TAG,
"Failed to check FreeRDP file descriptor");
658 listener->Close(listener);
659 shadow_subsystem_stop(server->subsystem);
663 if (shadow_client_boardcast_quit(server, 0))
665 while (ArrayList_Count(server->clients) > 0)
676static BOOL open_port(rdpShadowServer* server,
char* address)
679 char* modaddr = address;
683 if (modaddr[0] ==
'[')
685 char* end = strchr(address,
']');
688 WLog_ERR(TAG,
"Could not parse bind-address %s", address);
694 WLog_ERR(TAG,
"Excess data after IPv6 address: '%s'", end);
700 status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
705 "Problem creating TCP listener. (Port already used or insufficient permissions?)");
711int shadow_server_start(rdpShadowServer* server)
720 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
724 (void)signal(SIGPIPE, SIG_IGN);
726 server->screen = shadow_screen_new(server);
730 WLog_ERR(TAG,
"screen_new failed");
734 server->capture = shadow_capture_new(server);
736 if (!server->capture)
738 WLog_ERR(TAG,
"capture_new failed");
748 ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
749 strnlen(bind_address,
sizeof(bind_address))) != 0);
754 char** ptr = CommandLineParseCommaSeparatedValuesEx(
nullptr, server->ipcSocket, &count);
755 if (!ptr || (count <= 1))
757 if (server->ipcSocket ==
nullptr)
759 if (!open_port(server,
nullptr))
761 CommandLineParserFree(ptr);
767 CommandLineParserFree(ptr);
772 WINPR_ASSERT(ptr || (count == 0));
773 for (
size_t x = 1; x < count; x++)
775 BOOL success = open_port(server, ptr[x]);
778 CommandLineParserFree(ptr);
782 CommandLineParserFree(ptr);
786 status = server->listener->OpenLocal(server->listener, server->ipcSocket);
790 WLog_ERR(TAG,
"Problem creating local socket listener. (Port already used or "
791 "insufficient permissions?)");
796 if (!(server->thread =
797 CreateThread(
nullptr, 0, shadow_server_thread, (
void*)server, 0,
nullptr)))
805int shadow_server_stop(rdpShadowServer* server)
812 (void)SetEvent(server->StopEvent);
813 (void)WaitForSingleObject(server->thread, INFINITE);
814 (void)CloseHandle(server->thread);
815 server->thread =
nullptr;
816 if (server->listener && server->listener->Close)
817 server->listener->Close(server->listener);
822 shadow_screen_free(server->screen);
823 server->screen =
nullptr;
828 shadow_capture_free(server->capture);
829 server->capture =
nullptr;
836static int shadow_server_init_config_path(rdpShadowServer* server)
838 if (!server->ConfigPath)
844 if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome,
nullptr))
846 WLog_ERR(TAG,
"Failed to create directory '%s'", configHome);
851 server->ConfigPath = configHome;
855 if (!server->ConfigPath)
862static BOOL shadow_server_create_certificate(rdpShadowServer* server,
const char* filepath)
865 char* makecert_argv[6] = {
"makecert",
"-rdp",
"-live",
"-silent",
"-y",
"5" };
867 WINPR_STATIC_ASSERT(ARRAYSIZE(makecert_argv) <= INT_MAX);
868 const size_t makecert_argc = ARRAYSIZE(makecert_argv);
870 MAKECERT_CONTEXT* makecert = makecert_context_new();
875 if (makecert_context_process(makecert, (
int)makecert_argc, makecert_argv) < 0)
878 if (makecert_context_set_output_file_name(makecert,
"shadow") != 1)
881 WINPR_ASSERT(server);
882 WINPR_ASSERT(filepath);
883 if (!winpr_PathFileExists(server->CertificateFile))
885 if (makecert_context_output_certificate_file(makecert, filepath) != 1)
889 if (!winpr_PathFileExists(server->PrivateKeyFile))
891 if (makecert_context_output_private_key_file(makecert, filepath) != 1)
896 makecert_context_free(makecert);
901static BOOL shadow_server_init_certificate(rdpShadowServer* server)
903 char* filepath =
nullptr;
906 WINPR_ASSERT(server);
908 if (!winpr_PathFileExists(server->ConfigPath) &&
909 !winpr_PathMakePath(server->ConfigPath,
nullptr))
911 WLog_ERR(TAG,
"Failed to create directory '%s'", server->ConfigPath);
915 if (!(filepath = GetCombinedPath(server->ConfigPath,
"shadow")))
918 if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath,
nullptr))
920 if (!winpr_PathMakePath(filepath,
nullptr))
922 WLog_ERR(TAG,
"Failed to create directory '%s'", filepath);
927 server->CertificateFile = GetCombinedPath(filepath,
"shadow.crt");
928 server->PrivateKeyFile = GetCombinedPath(filepath,
"shadow.key");
930 if (!server->CertificateFile || !server->PrivateKeyFile)
933 if ((!winpr_PathFileExists(server->CertificateFile)) ||
934 (!winpr_PathFileExists(server->PrivateKeyFile)))
936 if (!shadow_server_create_certificate(server, filepath))
941 rdpSettings* settings = server->settings;
942 WINPR_ASSERT(settings);
945 rdpPrivateKey* key = freerdp_key_new_from_file_enc(server->PrivateKeyFile,
nullptr);
952 rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
959 if (!freerdp_certificate_is_rdp_security_compatible(cert))
976static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
978 WINPR_ASSERT(listener);
980 rdpShadowServer* server = (rdpShadowServer*)listener->info;
981 WINPR_ASSERT(server);
983 if (server->maxClientsConnected > 0)
985 const size_t count = ArrayList_Count(server->clients);
986 if (count >= server->maxClientsConnected)
988 WLog_WARN(TAG,
"connection limit [%" PRIuz
"] reached, discarding client",
989 server->maxClientsConnected);
996int shadow_server_init(rdpShadowServer* server)
999 if (!winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT))
1001 if (!WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()))
1004 if (!(server->clients = ArrayList_New(TRUE)))
1007 if (!(server->StopEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr)))
1010 if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
1013 status = shadow_server_init_config_path(server);
1018 if (!shadow_server_init_certificate(server))
1021 server->listener = freerdp_listener_new();
1023 if (!server->listener)
1026 server->listener->info = (
void*)server;
1027 server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
1028 server->listener->PeerAccepted = shadow_client_accepted;
1029 server->subsystem = shadow_subsystem_new();
1031 if (!server->subsystem)
1034 status = shadow_subsystem_init(server->subsystem, server);
1041 shadow_server_uninit(server);
1042 WLog_ERR(TAG,
"Failed to initialize shadow server");
1046int shadow_server_uninit(rdpShadowServer* server)
1051 shadow_server_stop(server);
1052 shadow_subsystem_uninit(server->subsystem);
1053 shadow_subsystem_free(server->subsystem);
1054 server->subsystem =
nullptr;
1055 freerdp_listener_free(server->listener);
1056 server->listener =
nullptr;
1057 free(server->CertificateFile);
1058 server->CertificateFile =
nullptr;
1059 free(server->PrivateKeyFile);
1060 server->PrivateKeyFile =
nullptr;
1061 free(server->ConfigPath);
1062 server->ConfigPath =
nullptr;
1063 DeleteCriticalSection(&(server->lock));
1064 (void)CloseHandle(server->StopEvent);
1065 server->StopEvent =
nullptr;
1066 ArrayList_Free(server->clients);
1067 server->clients =
nullptr;
1071rdpShadowServer* shadow_server_new(
void)
1073 rdpShadowServer* server =
nullptr;
1074 server = (rdpShadowServer*)calloc(1,
sizeof(rdpShadowServer));
1079 server->SupportMultiRectBitmapUpdates = TRUE;
1080 server->port = 3389;
1081 server->mayView = TRUE;
1082 server->mayInteract = TRUE;
1083 server->h264RateControlMode = H264_RATECONTROL_VBR;
1084 server->h264BitRate = 10000000;
1085 server->h264FrameRate = 30;
1087 server->authentication = TRUE;
1092void shadow_server_free(rdpShadowServer* server)
1097 free(server->ipcSocket);
1098 server->ipcSocket =
nullptr;
1100 server->settings =
nullptr;
FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.
WINPR_ATTR_NODISCARD FREERDP_API char * freerdp_settings_get_config_path(void)
return the configuration directory for the library
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
WINPR_ATTR_NODISCARD FREERDP_API rdpSettings * freerdp_settings_new(DWORD flags)
creates a new setting struct
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *val)
Sets a string settings value. The param is copied.